Initial commit

This commit is contained in:
user
2026-07-12 14:34:52 +04:00
commit 3abf6bd9c2
557 changed files with 68706 additions and 0 deletions
+294
View File
@@ -0,0 +1,294 @@
#include "application_connector.h"
#include "model_manager.h"
#include "repository_manager.h"
#include "schema_metadata.h"
#include <QObject>
ApplicationConnector::ApplicationConnector(QObject *rootObject)
: modelManager_(ModelManager::instance()),
repositoryManager_(RepositoryManager::instance()),
rootObject_(rootObject) {}
bool ApplicationConnector::connect() {
if (!connectSelfReferenceTableLinks()) {
return false;
}
if (!connectTablePairLinks()) {
return false;
}
if (!connectUpdateDeleteRFLinks()) {
return false;
}
if (!connectDeleteRFLinks()) {
return false;
}
return true;
}
ApplicationConnector::RMLinkList
ApplicationConnector::initializeSelfReferenceTableLinks() {
return {linkRepositoryToModel("genres", "genres"),
linkRepositoryToModel("halls", "halls"),
linkRepositoryToModel("movies", "movies"),
linkRepositoryToModel("sessions", "sessions"),
linkRepositoryToModel("refunds", "refunds"),
linkRepositoryToModel("tickets", "tickets")};
}
ApplicationConnector::RMLinkList
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
ApplicationConnector::flattenLinkList(const QVector<RFLinkList> &linkLists) {
RFLinkList result;
for (const RFLinkList &currentList : linkLists) {
std::move(currentList.cbegin(), currentList.cend(),
std::back_inserter(result));
}
return result;
}
ApplicationConnector::RFLinkList
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
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);
}
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;
}
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;
}
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;
}
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
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
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;
}
ApplicationConnector::RMLink ApplicationConnector::linkRepositoryToModel(
const QString &senderDatabaseTableName,
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);
}
ApplicationConnector::RFLinkList ApplicationConnector::linkRepositoryToFinder(
const QString &senderDatabaseTableName,
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
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;
}
QString ApplicationConnector::getAddress(const QObject *object) {
quintptr address = reinterpret_cast<quintptr>(object);
return object ? QString::number(address, 16) : "null";
};
QString ApplicationConnector::getMetaClassName(const QMetaObject *meta) {
return meta ? meta->className() : "Unknown";
};
QString ApplicationConnector::getMethodSignature(const QMetaObject *meta,
const char *methodType,
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;
};
+238
View File
@@ -0,0 +1,238 @@
#ifndef APPLICATION_CONNECTOR_H
#define APPLICATION_CONNECTOR_H
#include <QList>
#include <QPair>
#include <QVariant>
// WARNING: Not used directly in cpp file
// You can transfer it to the cpp file, it will not be an error, but a
// warning will remain
#include "base_sql_table_model.h"
#include "data_repository_signals.h"
#include "entity_finder_widget.h"
// Forward declarations
class ModelManager;
class RepositoryManager;
class ApplicationConnector {
private:
// WARNING: Not use directly in cpp file
template <typename SenderType, typename ReceiverType>
using Link = QPair<SenderType *, ReceiverType *>;
template <typename SignalType, typename SlotType>
using Connection = QPair<SignalType, SlotType>;
template <typename SenderType, typename ReceiverType>
using ConnectionVariant = std::variant<
Connection<void (SenderType::*)(), bool (ReceiverType::*)()>,
Connection<void (SenderType::*)() const, bool (ReceiverType::*)()>,
Connection<void (SenderType::*)(), void (ReceiverType::*)()>,
Connection<void (SenderType::*)() const, void (ReceiverType::*)()>>;
template <typename SenderType, typename ReceiverType>
using ConnectionList = QList<ConnectionVariant<SenderType, ReceiverType>>;
template <typename Sender, typename Receiver>
using LinkList = QList<Link<Sender, Receiver>>;
// Derivatives
using Repository = DataRepositorySignals;
using Model = BaseSqlTableModel;
using Finder = EntityFinderWidget;
// WARNING: Use only producing types in cpp file
// Producing types of Repository and Model
using RMLink = QPair<Repository *, Model *>;
using RMConnection = ConnectionVariant<Repository, Model>;
using RMConnectionList = QList<RMConnection>;
using RMLinkList = QList<RMLink>;
// Producing types of Repository and Finder
using RFLink = QPair<Repository *, Finder *>;
using RFConnection = ConnectionVariant<Repository, Finder>;
using RFConnectionList = QList<RFConnection>;
using RFLinkList = QList<RFLink>;
private:
ModelManager &modelManager_;
QObject *rootObject_;
RepositoryManager &repositoryManager_;
public:
ApplicationConnector(QObject *rootObject);
bool connect();
private:
template <typename SenderType, typename ReceiverType>
bool accessible(const Link<SenderType, ReceiverType> &link);
template <typename SignalType, typename SlotType, typename SenderType,
typename ReceiverType>
bool connect(SenderType *sender, ReceiverType *receiver,
const ConnectionList<SignalType, SlotType> &connections);
template <typename SignalType, typename SlotType, typename SenderType,
typename ReceiverType>
bool connect(const LinkList<SenderType, ReceiverType> &links,
const ConnectionList<SignalType, SlotType> &connections);
template <typename SignalType, typename SlotType>
QString templateString(SignalType signal, SlotType slot, QObject *sender,
QObject *receiver);
private:
RFConnectionList repositoryToFinderConnections();
RMConnectionList pairTableConnections();
RMConnectionList singleTableConnections();
RFLinkList flattenLinkList(const QVector<RFLinkList> &linkLists);
RFLinkList linkRepositoryToFinder(const QString &sender,
const QString &receiver);
RMLink linkRepositoryToModel(const QString &sender, const QString &receiver);
RFLinkList initializeDeleteCandidates();
RFLinkList initializeUpdateDeleteCandidates();
RMLinkList initializeSelfReferenceTableLinks();
RMLinkList initializeTablePairLinks();
bool connectDeleteRFLinks();
bool connectSelfReferenceTableLinks();
bool connectTablePairLinks();
bool connectUpdateDeleteRFLinks();
private:
QString getAddress(const QObject *object);
QString getMetaClassName(const QMetaObject *meta);
QString getMethodSignature(const QMetaObject *meta, const char *methodType,
bool isSignal);
};
template <typename SenderType, typename ReceiverType>
bool ApplicationConnector::accessible(
const Link<SenderType, ReceiverType> &link) {
return bool(link.first && link.second);
}
template <typename SignalType, typename SlotType, typename SenderType,
typename ReceiverType>
bool ApplicationConnector::connect(
const LinkList<SenderType, ReceiverType> &links,
const ConnectionList<SignalType, SlotType> &connections) {
for (const Link<SenderType, ReceiverType> &currentLink : links) {
qInfo() << "Connecting link: Index of ConnectionList:"
<< links.indexOf(currentLink);
if (!accessible(currentLink)) {
qWarning() << "Link is not accessible: Index of ConnectionList:"
<< links.indexOf(currentLink);
continue;
}
SenderType *sender = currentLink.first;
ReceiverType *receiver = currentLink.second;
if (!connect(sender, receiver, connections)) {
return false;
}
}
return true;
}
template <typename SignalType, typename SlotType, typename SenderType,
typename ReceiverType>
bool ApplicationConnector::connect(
SenderType *sender, ReceiverType *receiver,
const ConnectionList<SignalType, SlotType> &connections) {
// WARNING: "auto" keyword is necessary
auto handler = [&](const auto &currentLink) -> bool {
auto &[signal, slot] = currentLink;
bool connected = QObject::connect(sender, signal, receiver, slot);
if (!connected) {
qWarning().noquote() << "Failed to connect signal to slot:"
<< templateString(signal, slot, sender, receiver);
} else {
qInfo().noquote() << "Connected signal to slot:"
<< templateString(signal, slot, sender, receiver);
}
return connected;
};
for (const ConnectionVariant<SenderType, ReceiverType> &currentConnection :
connections) {
bool connected = std::visit(handler, currentConnection);
if (!connected) {
return false;
}
}
return true;
}
template <typename SignalType, typename SlotType>
QString ApplicationConnector::templateString(SignalType signal, SlotType slot,
QObject *sender,
QObject *receiver) {
const QMetaObject *senderMeta = sender ? sender->metaObject() : nullptr;
const QMetaObject *receiverMeta = receiver ? receiver->metaObject() : nullptr;
auto width = [](int index) {
switch (index) {
case 0:
return 10;
case 1:
return -30;
default:
return 0;
}
};
QString billetTA = QString("\n\t|%0: Type: %1 Address: 0x%2");
QString billetTAN = billetTA + QString(" Name: \"%3\"");
QString result;
QString signalT = getMethodSignature(senderMeta, typeid(signal).name(), true);
QString signalA = senderMeta ? getAddress(sender) : "null";
result +=
billetTA.arg("Signal", width(0)).arg(signalT, width(1)).arg(signalA);
QString slotT = getMethodSignature(receiverMeta, typeid(slot).name(), false);
QString slotA = receiverMeta ? getAddress(receiver) : "null";
result += billetTA.arg("Slot", width(0)).arg(slotT, width(1)).arg(slotA);
QString senderT = getMetaClassName(senderMeta);
QString senderA = getAddress(sender);
QString senderN = sender->objectName();
result += billetTAN.arg("Sender", width(0))
.arg(senderT, width(1))
.arg(senderA)
.arg(senderN);
QString receiverT = getMetaClassName(receiverMeta);
QString receiverA = getAddress(receiver);
QString receiverN = receiver->objectName();
result += billetTAN.arg("Receiver", width(0))
.arg(receiverT, width(1))
.arg(receiverA)
.arg(receiverN);
return result;
}
#endif // APPLICATION_CONNECTOR_H
+150
View File
@@ -0,0 +1,150 @@
#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;
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;
}
bool ApplicationController::ensureDatabaseExists(
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;
}
void ApplicationController::onLoginFailed() {
userId_ = -1;
accessLevel_ = 0;
}
void ApplicationController::onLoginSuccessful(qint32 userId,
qint32 accessLevel) {
userId_ = userId;
accessLevel_ = accessLevel;
}
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;
}
bool ApplicationController::loadTranslations(const QString &locale,
const QString &filename,
const QString &prefix,
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;
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef APPLICATION_CONTROLLER_H
#define APPLICATION_CONTROLLER_H
#include <QString>
class ApplicationController {
private:
static qint32 userId_;
static qint32 accessLevel_;
public:
explicit ApplicationController() = default;
static bool setupApplication();
static inline qint32 accessLevel();
static inline qint32 userId();
private slots:
static void onLoginFailed();
static void onLoginSuccessful(qint32 userId, qint32 accessLevel);
private:
// Database
static bool connectToDatabase(const QString &databaseFileName);
static bool ensureDatabaseExists(const QString &databaseFileName);
// Translations
static bool loadTranslations(const QString &locale, const QString &filename,
const QString &prefix, const QString &directory);
};
inline qint32 ApplicationController::accessLevel() { return accessLevel_; }
inline qint32 ApplicationController::userId() { return userId_; }
#endif // APPLICATION_CONTROLLER_H
+111
View File
@@ -0,0 +1,111 @@
#include "database_manager.h"
#include "config.h"
#include "execute_sql_file.h"
#include <QFile>
#include <QSqlError>
#include <qsqldatabase.h>
DatabaseManager::~DatabaseManager() {
disconnectDefaultDatabase();
QString defaultConnectionName = QSqlDatabase::database().connectionName();
QSqlDatabase::removeDatabase(QSqlDatabase::database().connectionName());
qInfo() << "Database with default connection name:" << defaultConnectionName
<< "was removed";
}
DatabaseManager::CreateDatabaseResult
DatabaseManager::createDefaultDatabaseIfMissing(
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;
}
bool DatabaseManager::connectToDefaultDatabase(
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;
}
void DatabaseManager::disconnectDefaultDatabase() {
QSqlDatabase defaultDatabase = QSqlDatabase::database();
if (defaultDatabase.isOpen()) {
defaultDatabase.close();
qInfo() << "Database disconnected";
}
}
bool DatabaseManager::executeSqlFiles(const QStringList &filePaths,
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;
}
+31
View File
@@ -0,0 +1,31 @@
#ifndef DATABASE_MANAGER_H
#define DATABASE_MANAGER_H
#include <QMutex>
#include <QSqlDatabase>
#include <QString>
class DatabaseManager {
public:
enum class CreateDatabaseResult { Success, AlreadyExists, Error };
private:
~DatabaseManager();
DatabaseManager(const DatabaseManager &) = delete;
DatabaseManager &operator=(const DatabaseManager &) = delete;
public:
static CreateDatabaseResult
createDefaultDatabaseIfMissing(const QString &databaseFileName);
static bool connectToDefaultDatabase(const QString &databaseFileName);
static void disconnectDefaultDatabase();
private:
static bool databaseFileExists(const QString &databaseName, QSqlDatabase &db);
static bool executeSqlFiles(const QStringList &filePaths, QSqlDatabase &db);
};
#endif // DATABASE_MANAGER_H
+108
View File
@@ -0,0 +1,108 @@
#include "execute_sql_file.h"
#include <QRegularExpression>
ExecuteSqlFile::ExecuteSqlFile(QSqlDatabase &db)
: db_(db), delimiter_(";") { // Default delimiter is ";"
// Constructor does not open/close the connection
}
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);
}
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;
}
bool ExecuteSqlFile::processLine(QString &line, QString &currentStatement) {
if (!filterLine(line)) {
qInfo() << "Line is empty after filtering";
return true; // Line was empty after filtering
}
if (processDelimiterChange(line)) {
qInfo() << "Delimiter changed to:" << delimiter_;
return true;
}
currentStatement += line + " ";
auto delimiterIndex = currentStatement.lastIndexOf(delimiter_);
if (delimiterIndex == -1) {
qInfo() << "Delimiter not found, current statement is not finished";
return true;
}
qInfo() << "Statement finished, delimiter found at index:" << delimiterIndex;
// Execute the current statement
QString completedStatement =
currentStatement.replace(delimiter_, ";").trimmed();
if (!executeStatement(completedStatement)) {
qWarning() << "Failed to execute statement:" << completedStatement;
return false; // Execution failed
}
qInfo() << "Successfully executed statement:" << completedStatement;
currentStatement.clear();
return true;
}
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;
}
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
}
bool ExecuteSqlFile::executeStatement(const QString &statement) const {
QSqlQuery query(db_);
if (!query.exec(statement)) {
qWarning() << "Error:" << query.lastError().text();
return false;
}
return true;
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef EXECUTE_SQL_FILE_H
#define EXECUTE_SQL_FILE_H
#include <QDebug>
#include <QFile>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QTextStream>
class ExecuteSqlFile {
private:
QSqlDatabase &db_; // Reference to the database handler
// Tracks if the parser is inside a trigger block
bool insideTrigger_ = false;
// Delimiter for SQL statements
QString delimiter_;
public:
// Constructor to initialize the database handler
explicit ExecuteSqlFile(QSqlDatabase &db);
// Executes the SQL commands from the given file
bool operator()(const QString &filePath);
private:
// Executes a single SQL statement
bool executeStatement(const QString &statement) const;
// Filter out comments and empty lines
bool filterLine(QString &line);
// Processes the DELIMITER command
bool processDelimiterChange(QString &line);
// Processes a single line of SQL and updates the current state
bool processLine(QString &line, QString &currentStatement);
// Processes a block of SQL statements, handling triggers separately
bool processStatements(QStringList &statements);
};
#endif // EXECUTE_SQL_FILE_H
+26
View File
@@ -0,0 +1,26 @@
#ifndef TABLE_INFO_INTERFACE_H
#define TABLE_INFO_INTERFACE_H
#include <QMetaType>
#include <QString>
class TableInfoInterface {
public:
virtual ~TableInfoInterface() {}
virtual QMetaType::Type
columnMetaType(const QString &columnName) const noexcept = 0;
virtual QString columnName(qint32 columnIndex) const noexcept = 0;
virtual QString databaseTableName() const noexcept = 0;
virtual QStringList columnNames() const noexcept = 0;
virtual qint32 columnIndex(const QString &columnName) const noexcept = 0;
virtual void
setDatabaseTableName(const QString &databaseTableName) noexcept = 0;
};
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef GENRE_DTO_H
#define GENRE_DTO_H
#include <QString>
class GenreDTO {
private:
QString genreName_; // Genre name
qint32 genreId_; // Unique ID for the genre
public:
// Constructors
explicit GenreDTO() = default;
explicit GenreDTO(const QString &genreName, qint32 genreId)
: genreId_(genreId), genreName_(genreName) {}
// Setters (inline methods)
inline void setGenreId(qint32 genreId) { genreId_ = genreId; }
inline void setGenreName(const QString &genreName) { genreName_ = genreName; }
// Getters (inline methods)
inline qint32 genreId() const { return genreId_; }
inline QString genreName() const { return genreName_; }
};
#endif // GENRE_DTO_H
+29
View File
@@ -0,0 +1,29 @@
#ifndef HALL_DTO_H
#define HALL_DTO_H
#include <QString>
class HallDTO {
private:
QString hallName_; // Hall name
qint32 capacity_; // Hall capacity
qint32 hallId_; // Unique ID for the hall
public:
// Constructors
explicit HallDTO() = default;
explicit HallDTO(const QString &hallName, qint32 capacity, qint32 hallId)
: hallName_(hallName), capacity_(capacity), hallId_(hallId) {}
// Setters (inline methods)
inline void setHallName(const QString &hallName) { hallName_ = hallName; }
inline void setCapacity(qint32 capacity) { capacity_ = capacity; }
inline void setHallId(qint32 hallId) { hallId_ = hallId; }
// Getters (inline methods)
inline QString hallName() const { return hallName_; }
inline qint32 capacity() const { return capacity_; }
inline qint32 hallId() const { return hallId_; }
};
#endif // HALL_DTO_H
+35
View File
@@ -0,0 +1,35 @@
#ifndef MOVIE_DTO_H
#define MOVIE_DTO_H
#include <QString>
class MovieDTO {
private:
QString title_; // Movie title
qint32 duration_; // Movie duration in minutes
qint32 genreId_; // Genre ID
qint32 movieId_; // Unique ID for the movie
public:
// Constructors
explicit MovieDTO() = default;
explicit MovieDTO(const QString &title, qint32 duration, qint32 genreId,
qint32 movieId)
: title_(title), duration_(duration), genreId_(genreId),
movieId_(movieId) {}
// Setters (inline methods)
inline void setTitle(const QString &title) { title_ = title; }
inline void setDuration(qint32 duration) { duration_ = duration; }
inline void setGenreId(qint32 genreId) { genreId_ = genreId; }
inline void setMovieId(qint32 movieId) { movieId_ = movieId; }
// Getters (inline methods)
inline QString title() const { return title_; }
inline qint32 duration() const { return duration_; }
inline qint32 genreId() const { return genreId_; }
inline qint32 movieId() const { return movieId_; }
};
#endif // MOVIE_DTO_H
+37
View File
@@ -0,0 +1,37 @@
#ifndef REFUND_DTO_H
#define REFUND_DTO_H
#include <QDateTime>
class RefundDTO {
private:
QDateTime refundAt_; // Refund date and time
double refundAmount_; // Refund amount
qint32 refundId_; // Unique ID for the refund
qint32 ticketId_; // Associated ticket ID
public:
// Constructors
explicit RefundDTO() = default;
explicit RefundDTO(const QDateTime &refundAt, double refundAmount,
qint32 refundId, qint32 ticketId)
: refundAt_(refundAt), refundAmount_(refundAmount), refundId_(refundId),
ticketId_(ticketId) {}
// Setters (inline methods)
inline void setRefundAt(const QDateTime &refundAt) { refundAt_ = refundAt; }
inline void setRefundAmount(double refundAmount) {
refundAmount_ = refundAmount;
}
inline void setRefundId(qint32 refundId) { refundId_ = refundId; }
inline void setTicketId(qint32 ticketId) { ticketId_ = ticketId; }
// Getters (inline methods)
inline QDateTime refundAt() const { return refundAt_; }
inline double refundAmount() const { return refundAmount_; }
inline qint32 refundId() const { return refundId_; }
inline qint32 ticketId() const { return ticketId_; }
};
#endif // REFUND_DTO_H
+30
View File
@@ -0,0 +1,30 @@
#ifndef ROLE_DTO_H
#define ROLE_DTO_H
#include <QString>
class RoleDTO {
private:
QString roleName_; // Name of the role
qint32 accessLevel_; // Access level (permissions)
qint32 roleId_; // Unique ID for the role
public:
// Constructors
explicit RoleDTO() = default;
explicit RoleDTO(const QString &roleName, qint32 accessLevel, qint32 roleId)
: roleName_(roleName), accessLevel_(accessLevel), roleId_(roleId) {}
// Setters (inline methods)
inline void setRoleName(const QString &roleName) { roleName_ = roleName; }
inline void setAccessLevel(qint32 accessLevel) { accessLevel_ = accessLevel; }
inline void setRoleId(qint32 roleId) { roleId_ = roleId; }
// Getters (inline methods)
inline QString roleName() const { return roleName_; }
inline qint32 accessLevel() const { return accessLevel_; }
inline qint32 roleId() const { return roleId_; }
};
#endif // ROLE_DTO_H
+38
View File
@@ -0,0 +1,38 @@
#ifndef SESSION_DTO_H
#define SESSION_DTO_H
#include <QDateTime>
class SessionDTO {
private:
QDateTime beginAt_; // Session start time
double ticketPrice_; // Ticket price
qint32 hallId_; // Associated hall ID
qint32 movieId_; // Associated movie ID
qint32 sessionId_; // Unique session ID
public:
// Constructors
explicit SessionDTO() = default;
explicit SessionDTO(const QDateTime &beginAt, double ticketPrice,
qint32 hallId, qint32 movieId, qint32 sessionId)
: beginAt_(beginAt), ticketPrice_(ticketPrice), hallId_(hallId),
movieId_(movieId), sessionId_(sessionId) {}
// Setters (inline methods)
inline void setBeginAt(const QDateTime &beginAt) { beginAt_ = beginAt; }
inline void setTicketPrice(double ticketPrice) { ticketPrice_ = ticketPrice; }
inline void setHallId(qint32 hallId) { hallId_ = hallId; }
inline void setMovieId(qint32 movieId) { movieId_ = movieId; }
inline void setSessionId(qint32 sessionId) { sessionId_ = sessionId; }
// Getters (inline methods)
inline QDateTime beginAt() const { return beginAt_; }
inline double ticketPrice() const { return ticketPrice_; }
inline qint32 hallId() const { return hallId_; }
inline qint32 movieId() const { return movieId_; }
inline qint32 sessionId() const { return sessionId_; }
};
#endif // SESSION_DTO_H
+81
View File
@@ -0,0 +1,81 @@
#ifndef SESSION_STATISTICS_DTO_H
#define SESSION_STATISTICS_DTO_H
#include <QDateTime>
#include <QString>
class SessionStatisticsDTO {
private:
qint32 sessionId_;
QDateTime beginAt_;
double ticketPrice_;
qint32 hallId_;
QString hallName_;
qint32 totalSeats_;
qint32 movieId_;
QString movieTitle_;
qint32 duration_;
qint32 genreId_;
QString genreName_;
qint32 soldTickets_;
qint32 refundedTickets_;
double salesRevenue_;
double refundExpenses_;
public:
SessionStatisticsDTO() = default;
SessionStatisticsDTO(QDateTime beginAt, QString genreName, QString hallName,
QString movieTitle, double refundExpenses,
double salesRevenue, double ticketPrice, qint32 duration,
qint32 genreId, qint32 hallId, qint32 movieId,
qint32 refundedTickets, qint32 sessionId,
qint32 soldTickets, qint32 totalSeats)
: beginAt_(beginAt), duration_(duration), genreId_(genreId),
genreName_(genreName), hallId_(hallId), hallName_(hallName),
movieId_(movieId), movieTitle_(movieTitle),
refundExpenses_(refundExpenses), refundedTickets_(refundedTickets),
salesRevenue_(salesRevenue), sessionId_(sessionId),
soldTickets_(soldTickets), ticketPrice_(ticketPrice),
totalSeats_(totalSeats) {}
// clang-format off
// Getters
inline QDateTime beginAt() const { return beginAt_; }
inline QString genreName() const { return genreName_; }
inline QString hallName() const { return hallName_; }
inline QString movieTitle() const { return movieTitle_; }
inline double refundExpenses() const { return refundExpenses_; }
inline double salesRevenue() const { return salesRevenue_; }
inline double ticketPrice() const { return ticketPrice_; }
inline qint32 duration() const { return duration_; }
inline qint32 genreId() const { return genreId_; }
inline qint32 hallId() const { return hallId_; }
inline qint32 movieId() const { return movieId_; }
inline qint32 refundedTickets() const { return refundedTickets_; }
inline qint32 sessionId() const { return sessionId_; }
inline qint32 soldTickets() const { return soldTickets_; }
inline qint32 totalSeats() const { return totalSeats_; }
// Setters
inline void setBeginAt(QDateTime beginAt) { beginAt_ = beginAt; }
inline void setDuration(qint32 duration) { duration_ = duration; }
inline void setGenreId(qint32 genreId) { genreId_ = genreId; }
inline void setGenreName(QString genreName) { genreName_ = genreName; }
inline void setHallId(qint32 hallId) { hallId_ = hallId; }
inline void setHallName(QString hallName) { hallName_ = hallName; }
inline void setMovieId(qint32 movieId) { movieId_ = movieId; }
inline void setMovieTitle(QString movieTitle) { movieTitle_ = movieTitle; }
inline void setRefundExpenses(double refundExpenses) { refundExpenses_ = refundExpenses; }
inline void setRefundedTickets(qint32 refundedTickets) { refundedTickets_ = refundedTickets; }
inline void setSalesRevenue(double salesRevenue) { salesRevenue_ = salesRevenue; }
inline void setSessionId(qint32 sessionId) { sessionId_ = sessionId; }
inline void setSoldTickets(qint32 soldTickets) { soldTickets_ = soldTickets; }
inline void setTicketPrice(double ticketPrice) { ticketPrice_ = ticketPrice; }
inline void setTotalSeats(qint32 totalSeats) { totalSeats_ = totalSeats; }
// clang-format on
};
#endif // SESSION_STATISTICS_DTO_H
+35
View File
@@ -0,0 +1,35 @@
#ifndef TICKET_DTO_H
#define TICKET_DTO_H
#include <QDateTime>
class TicketDTO {
private:
QDateTime soldAt_; // Ticket sold time
qint32 seatNumber_; // Seat number
qint32 sessionId_; // Session ID
qint32 ticketId_; // Unique ticket ID
public:
// Constructors
explicit TicketDTO() = default;
explicit TicketDTO(const QDateTime &soldAt, qint32 seatNumber,
qint32 sessionId, qint32 ticketId)
: soldAt_(soldAt), seatNumber_(seatNumber), sessionId_(sessionId),
ticketId_(ticketId) {}
// Setters (inline methods)
inline void setSeatNumber(qint32 seatNumber) { seatNumber_ = seatNumber; }
inline void setSessionId(qint32 sessionId) { sessionId_ = sessionId; }
inline void setSoldAt(const QDateTime &soldAt) { soldAt_ = soldAt; }
inline void setTicketId(qint32 ticketId) { ticketId_ = ticketId; }
// Getters (inline methods)
inline QDateTime soldAt() const { return soldAt_; }
inline qint32 seatNumber() const { return seatNumber_; }
inline qint32 sessionId() const { return sessionId_; }
inline qint32 ticketId() const { return ticketId_; }
};
#endif // TICKET_DTO_H
+40
View File
@@ -0,0 +1,40 @@
#ifndef USER_DTO_H
#define USER_DTO_H
#include <QString>
class UserDTO {
private:
QString passwordHash_; // Hashed password
QString salt_; // Salt for password hashing
QString username_; // Username
qint32 roleId_; // Role ID
qint32 userId_; // Unique user ID
public:
// Constructors
explicit UserDTO() = default;
explicit UserDTO(const QString &passwordHash, const QString &salt,
const QString &username, qint32 roleId, qint32 userId)
: passwordHash_(passwordHash), roleId_(roleId), salt_(salt),
userId_(userId), username_(username) {}
// Setters (inline methods)
inline void setPasswordHash(const QString &passwordHash) {
passwordHash_ = passwordHash;
}
inline void setRoleId(qint32 roleId) { roleId_ = roleId; }
inline void setSalt(const QString &salt) { salt_ = salt; }
inline void setUserId(qint32 userId) { userId_ = userId; }
inline void setUsername(const QString &username) { username_ = username; }
// Getters (inline methods)
inline QString passwordHash() const { return passwordHash_; }
inline QString salt() const { return salt_; }
inline QString username() const { return username_; }
inline qint32 roleId() const { return roleId_; }
inline qint32 userId() const { return userId_; }
};
#endif // USER_DTO_H
@@ -0,0 +1,34 @@
#ifndef GENRES_REPOSITORY_INTERFACE_H
#define GENRES_REPOSITORY_INTERFACE_H
#include "repository_interface.h"
#include <QVector>
// Forward declarations
struct CreateGenreRequest;
struct GenreDTO;
struct UpdateGenreRequest;
struct RepositoryResult;
// Interface for genres repository
class GenresRepositoryInterface : public RepositoryInterface {
public:
// Create a new genre
virtual RepositoryResult createGenre(const CreateGenreRequest &request) = 0;
// Retrieve a genre by ID
virtual RepositoryResult getGenreById(qint32 genreId,
GenreDTO &genre) const = 0;
// Retrieve all genres
virtual RepositoryResult getAllGenres(QVector<GenreDTO> &genres) const = 0;
// Update an existing genre
virtual RepositoryResult updateGenre(const UpdateGenreRequest &request) = 0;
// Delete a genre by ID
virtual RepositoryResult deleteGenreById(qint32 genreId) = 0;
};
#endif // GENRES_REPOSITORY_INTERFACE_H
@@ -0,0 +1,33 @@
#ifndef HALLS_REPOSITORY_INTERFACE_H
#define HALLS_REPOSITORY_INTERFACE_H
#include "repository_interface.h"
#include <QVector>
// Forward declarations
struct CreateHallRequest;
struct HallDTO;
struct RepositoryResult;
struct UpdateHallRequest;
// Interface for halls repository
class HallsRepositoryInterface : public RepositoryInterface {
public:
// Create a new hall
virtual RepositoryResult createHall(const CreateHallRequest &request) = 0;
// Retrieve a hall by ID
virtual RepositoryResult getHallById(qint32 hallId, HallDTO &hall) const = 0;
// Retrieve all halls
virtual RepositoryResult getAllHalls(QVector<HallDTO> &halls) const = 0;
// Update an existing hall
virtual RepositoryResult updateHall(const UpdateHallRequest &request) = 0;
// Delete a hall by ID
virtual RepositoryResult deleteHallById(qint32 hallId) = 0;
};
#endif // HALLS_REPOSITORY_INTERFACE_H
@@ -0,0 +1,34 @@
#ifndef MOVIES_REPOSITORY_INTERFACE_H
#define MOVIES_REPOSITORY_INTERFACE_H
#include "repository_interface.h"
#include <QVector>
// Forward declarations
struct CreateMovieRequest;
struct MovieDTO;
struct RepositoryResult;
struct UpdateMovieRequest;
// Interface for movies repository
class MoviesRepositoryInterface : public RepositoryInterface {
public:
// Create a new movie
virtual RepositoryResult createMovie(const CreateMovieRequest &request) = 0;
// Retrieve a movie by ID
virtual RepositoryResult getMovieById(qint32 movieId,
MovieDTO &movie) const = 0;
// Retrieve all movies
virtual RepositoryResult getAllMovies(QVector<MovieDTO> &movies) const = 0;
// Update an existing movie
virtual RepositoryResult updateMovie(const UpdateMovieRequest &request) = 0;
// Delete a movie by ID
virtual RepositoryResult deleteMovieById(qint32 movieId) = 0;
};
#endif // MOVIES_REPOSITORY_INTERFACE_H
@@ -0,0 +1,34 @@
#ifndef REFUNDS_REPOSITORY_INTERFACE_H
#define REFUNDS_REPOSITORY_INTERFACE_H
#include "repository_interface.h"
#include <QVector>
// Forward declarations
struct CreateRefundRequest;
struct RefundDTO;
struct RepositoryResult;
struct UpdateRefundRequest;
// Interface for refunds repository
class RefundsRepositoryInterface : public RepositoryInterface {
public:
// Create a new refund
virtual RepositoryResult createRefund(const CreateRefundRequest &request) = 0;
// Retrieve a refund by ID
virtual RepositoryResult getRefundById(qint32 refundId,
RefundDTO &refund) const = 0;
// Retrieve all refunds
virtual RepositoryResult getAllRefunds(QVector<RefundDTO> &refunds) const = 0;
// Update an existing refund
virtual RepositoryResult updateRefund(const UpdateRefundRequest &request) = 0;
// Delete a refund by ID
virtual RepositoryResult deleteRefundById(qint32 refundId) = 0;
};
#endif // REFUNDS_REPOSITORY_INTERFACE_H
+11
View File
@@ -0,0 +1,11 @@
#ifndef REPOSITORY_INTERFACE_H
#define REPOSITORY_INTERFACE_H
#include <QVariant>
class RepositoryInterface {
public:
virtual ~RepositoryInterface() = default;
};
#endif // REPOSITORY_INTERFACE_H
+7
View File
@@ -0,0 +1,7 @@
#include "repository_result.h"
RepositoryResult RepositoryResult::successResult() { return {"", true}; }
RepositoryResult RepositoryResult::failureResult(const QString &error) {
return {error, false};
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef REPOSITORY_RESULT_H
#define REPOSITORY_RESULT_H
#include <QString>
// Represents the result of an operation in a repository
struct RepositoryResult {
QString errorMessage; // Error message if the operation failed
bool success; // Indicates whether the operation was successful
// Creates a successful result
static RepositoryResult successResult();
// Creates a failed result with an error message
static RepositoryResult failureResult(const QString &error);
};
#endif // REPOSITORY_RESULT_H
@@ -0,0 +1,33 @@
#ifndef ROLES_REPOSITORY_INTERFACE_H
#define ROLES_REPOSITORY_INTERFACE_H
#include "create_role_request.h"
#include "repository_interface.h"
#include "update_role_request.h"
#include <QVector>
// Forward declarations
struct RepositoryResult;
struct RoleDTO;
// Interface for sessions repository
class RolesRepositoryInterface : public RepositoryInterface {
public:
// Create a new role
virtual RepositoryResult createRole(const CreateRoleRequest &request) = 0;
// Retrieve a role by ID
virtual RepositoryResult getRoleById(qint32 roleId, RoleDTO &role) const = 0;
// Retrieve all roles
virtual RepositoryResult getAllRoles(QVector<RoleDTO> &roles) const = 0;
// Update an existing role
virtual RepositoryResult updateRole(const UpdateRoleRequest &request) = 0;
// Delete a role by ID
virtual RepositoryResult deleteRoleById(qint32 roleId) = 0;
};
#endif // ROLES_REPOSITORY_INTERFACE_H
@@ -0,0 +1,37 @@
#ifndef SESSIONS_REPOSITORY_INTERFACE_H
#define SESSIONS_REPOSITORY_INTERFACE_H
#include "repository_interface.h"
#include <QVector>
// Forward declarations
struct CreateSessionRequest;
struct RepositoryResult;
struct SessionDTO;
struct UpdateSessionRequest;
// Interface for sessions repository
class SessionsRepositoryInterface : public RepositoryInterface {
public:
// Create a new session
virtual RepositoryResult
createSession(const CreateSessionRequest &request) = 0;
// Retrieve a session by ID
virtual RepositoryResult getSessionById(qint32 sessionId,
SessionDTO &session) const = 0;
// Retrieve all sessions
virtual RepositoryResult
getAllSessions(QVector<SessionDTO> &sessions) const = 0;
// Update an existing session
virtual RepositoryResult
updateSession(const UpdateSessionRequest &request) = 0;
// Delete a session by ID
virtual RepositoryResult deleteSessionById(qint32 sessionId) = 0;
};
#endif // SESSIONS_REPOSITORY_INTERFACE_H
@@ -0,0 +1,38 @@
#ifndef SESSIONS_STATICTICS_REPOSITORY_INTERFACE_H
#define SESSIONS_STATICTICS_REPOSITORY_INTERFACE_H
#include "repository_interface.h"
#include <QDateTime>
#include <QVector>
// Forward declaration
class SessionStatisticsDTO;
class RepositoryResult;
class SessionsStatisticsRepositoryInterface : public RepositoryInterface {
public:
virtual ~SessionsStatisticsRepositoryInterface() = default;
virtual RepositoryResult
getSessionById(qint32 sessionid, SessionStatisticsDTO &sessionInfo) const = 0;
virtual RepositoryResult
getSessionsForGenre(qint32 genreId, std::optional<QDateTime> startDate,
std::optional<QDateTime> endDate,
QVector<SessionStatisticsDTO> &sessions) const = 0;
virtual RepositoryResult
getSessionsForHall(qint32 hallId, std::optional<QDateTime> startDate,
std::optional<QDateTime> endDate,
QVector<SessionStatisticsDTO> &sessions) const = 0;
virtual RepositoryResult
getSessionsForMovie(qint32 movieId, std::optional<QDateTime> startDate,
std::optional<QDateTime> endDate,
QVector<SessionStatisticsDTO> &sessions) const = 0;
virtual RepositoryResult
getAllSessions(QVector<SessionStatisticsDTO> &sessions) const = 0;
};
#endif // SESSIONS_STATICTICS_REPOSITORY_INTERFACE_H
@@ -0,0 +1,34 @@
#ifndef TICKETS_REPOSITORY_INTERFACE_H
#define TICKETS_REPOSITORY_INTERFACE_H
#include "repository_interface.h"
#include <QVector>
// Forward declarations
struct CreateTicketRequest;
struct TicketDTO;
struct RepositoryResult;
struct UpdateTicketRequest;
// Interface for tickets repository
class TicketsRepositoryInterface : public RepositoryInterface {
public:
// Create a new ticket
virtual RepositoryResult createTicket(const CreateTicketRequest &request) = 0;
// Retrieve a ticket by ID
virtual RepositoryResult getTicketById(qint32 ticketId,
TicketDTO &ticket) const = 0;
// Retrieve all tickets
virtual RepositoryResult getAllTickets(QVector<TicketDTO> &tickets) const = 0;
// Update an existing ticket
virtual RepositoryResult updateTicket(const UpdateTicketRequest &request) = 0;
// Delete a ticket by ID
virtual RepositoryResult deleteTicketById(qint32 ticketId) = 0;
};
#endif // TICKETS_REPOSITORY_INTERFACE_H
@@ -0,0 +1,37 @@
#ifndef USERS_REPOSITORY_INTERFACE_H
#define USERS_REPOSITORY_INTERFACE_H
#include "create_user_request.h"
#include "repository_interface.h"
#include "update_user_request.h"
#include <QVector>
// Forward declarations
struct RepositoryResult;
struct UserDTO;
// Interface for sessions repository
class UsersRepositoryInterface : public RepositoryInterface {
public:
// Create a new user
virtual RepositoryResult createUser(const CreateUserRequest &request) = 0;
// Retrieve a user by ID
virtual RepositoryResult getUserById(qint32 userId, UserDTO &user) const = 0;
// Retrieve a user by username
virtual RepositoryResult getUserByUsername(const QString &username,
UserDTO &user) const = 0;
// Retrieve all users
virtual RepositoryResult getAllUsers(QVector<UserDTO> &users) const = 0;
// Update an existing user
virtual RepositoryResult updateUser(const UpdateUserRequest &request) = 0;
// Delete a user by ID
virtual RepositoryResult deleteUserById(qint32 userId) = 0;
};
#endif // USERS_REPOSITORY_INTERFACE_H
+24
View File
@@ -0,0 +1,24 @@
#ifndef CREATE_GENRE_REQUEST_H
#define CREATE_GENRE_REQUEST_H
#include <QString>
class CreateGenreRequest {
private:
QString genreName_; // Genre name
public:
// Constructors
explicit CreateGenreRequest() = default;
explicit CreateGenreRequest(const QString &genreName)
: genreName_(genreName) {}
// Setters (inline methods)
inline void setGenreName(const QString &genreName) { genreName_ = genreName; }
// Getters (inline methods)
inline QString genreName() const { return genreName_; }
};
#endif // CREATE_GENRE_REQUEST_H
+26
View File
@@ -0,0 +1,26 @@
#ifndef CREATE_HALL_REQUEST_H
#define CREATE_HALL_REQUEST_H
#include <QString>
class CreateHallRequest {
private:
QString hallName_; // Hall name
qint32 capacity_; // Hall capacity
public:
// Constructors
explicit CreateHallRequest() = default;
explicit CreateHallRequest(const QString &hallName, qint32 capacity)
: hallName_(hallName), capacity_(capacity) {}
// Setters (inline methods)
inline void setHallName(const QString &hallName) { hallName_ = hallName; }
inline void setCapacity(qint32 capacity) { capacity_ = capacity; }
// Getters (inline methods)
inline QString hallName() const { return hallName_; }
inline qint32 capacity() const { return capacity_; }
};
#endif // CREATE_HALL_REQUEST_H
+30
View File
@@ -0,0 +1,30 @@
#ifndef CREATE_MOVIE_REQUEST_H
#define CREATE_MOVIE_REQUEST_H
#include <QString>
class CreateMovieRequest {
private:
QString title_; // Movie title
qint32 duration_; // Movie duration
qint32 genreId_; // Genre ID
public:
// Constructors
explicit CreateMovieRequest() = default;
explicit CreateMovieRequest(const QString &title, qint32 duration,
qint32 genreId)
: title_(title), duration_(duration), genreId_(genreId) {}
// Setters (inline methods)
inline void setTitle(const QString &title) { title_ = title; }
inline void setDuration(qint32 duration) { duration_ = duration; }
inline void setGenreId(qint32 genreId) { genreId_ = genreId; }
// Getters (inline methods)
inline QString title() const { return title_; }
inline qint32 duration() const { return duration_; }
inline qint32 genreId() const { return genreId_; }
};
#endif // CREATE_MOVIE_REQUEST_H
+32
View File
@@ -0,0 +1,32 @@
#ifndef CREATE_REFUND_REQUEST_H
#define CREATE_REFUND_REQUEST_H
#include <QDateTime>
class CreateRefundRequest {
private:
QDateTime refundAt_; // Refund date and time
double refundAmount_; // Refund amount
qint32 ticketId_; // Ticket ID
public:
// Constructors
explicit CreateRefundRequest() = default;
explicit CreateRefundRequest(const QDateTime &refundAt, double refundAmount,
qint32 ticketId)
: refundAt_(refundAt), refundAmount_(refundAmount), ticketId_(ticketId) {}
// Setters (inline methods)
inline void setRefundAt(const QDateTime &refundAt) { refundAt_ = refundAt; }
inline void setRefundAmount(double refundAmount) {
refundAmount_ = refundAmount;
}
inline void setTicketId(qint32 ticketId) { ticketId_ = ticketId; }
// Getters (inline methods)
inline QDateTime refundAt() const { return refundAt_; }
inline double refundAmount() const { return refundAmount_; }
inline qint32 ticketId() const { return ticketId_; }
};
#endif // CREATE_REFUND_REQUEST_H
+27
View File
@@ -0,0 +1,27 @@
#ifndef CREATE_ROLE_REQUEST_H
#define CREATE_ROLE_REQUEST_H
#include <QString>
class CreateRoleRequest {
private:
QString roleName_; // Role name
qint32 accessLevel_; // Access level
public:
// Constructors
explicit CreateRoleRequest() = default;
explicit CreateRoleRequest(const QString &roleName, qint32 accessLevel)
: roleName_(roleName), accessLevel_(accessLevel) {}
// Setters (inline methods)
inline void setRoleName(const QString &roleName) { roleName_ = roleName; }
inline void setAccessLevel(qint32 accessLevel) { accessLevel_ = accessLevel; }
// Getters (inline methods)
inline QString roleName() const { return roleName_; }
inline qint32 accessLevel() const { return accessLevel_; }
};
#endif // CREATE_ROLE_REQUEST_H
+34
View File
@@ -0,0 +1,34 @@
#ifndef CREATE_SESSION_REQUEST_H
#define CREATE_SESSION_REQUEST_H
#include <QDateTime>
class CreateSessionRequest {
private:
QDateTime beginAt_; // Session start time
double ticketPrice_; // Ticket price
qint32 hallId_; // Hall ID
qint32 movieId_; // Movie ID
public:
// Constructors
explicit CreateSessionRequest() = default;
explicit CreateSessionRequest(const QDateTime &beginAt, double ticketPrice,
qint32 hallId, qint32 movieId)
: beginAt_(beginAt), ticketPrice_(ticketPrice), hallId_(hallId),
movieId_(movieId) {}
// Setters (inline methods)
inline void setBeginAt(const QDateTime &beginAt) { beginAt_ = beginAt; }
inline void setTicketPrice(double ticketPrice) { ticketPrice_ = ticketPrice; }
inline void setHallId(qint32 hallId) { hallId_ = hallId; }
inline void setMovieId(qint32 movieId) { movieId_ = movieId; }
// Getters (inline methods)
inline QDateTime beginAt() const { return beginAt_; }
inline double ticketPrice() const { return ticketPrice_; }
inline qint32 hallId() const { return hallId_; }
inline qint32 movieId() const { return movieId_; }
};
#endif // CREATE_SESSION_REQUEST_H
+30
View File
@@ -0,0 +1,30 @@
#ifndef CREATE_TICKET_REQUEST_H
#define CREATE_TICKET_REQUEST_H
#include <QDateTime>
class CreateTicketRequest {
private:
QDateTime soldAt_; // Ticket sold date and time
qint32 seatNumber_; // Seat number
qint32 sessionId_; // Session ID
public:
// Constructors
explicit CreateTicketRequest() = default;
explicit CreateTicketRequest(const QDateTime &soldAt, qint32 seatNumber,
qint32 sessionId)
: soldAt_(soldAt), seatNumber_(seatNumber), sessionId_(sessionId) {}
// Setters (inline methods)
inline void setSoldAt(const QDateTime &soldAt) { soldAt_ = soldAt; }
inline void setSeatNumber(qint32 seatNumber) { seatNumber_ = seatNumber; }
inline void setSessionId(qint32 sessionId) { sessionId_ = sessionId; }
// Getters (inline methods)
inline QDateTime soldAt() const { return soldAt_; }
inline qint32 seatNumber() const { return seatNumber_; }
inline qint32 sessionId() const { return sessionId_; }
};
#endif // CREATE_TICKET_REQUEST_H
+36
View File
@@ -0,0 +1,36 @@
#ifndef CREATE_USER_REQUEST_H
#define CREATE_USER_REQUEST_H
#include <QString>
class CreateUserRequest {
private:
QString passwordHash_; // Hashed password
QString salt_; // Salt for password hashing
QString username_; // Username
qint32 roleId_; // Role ID
public:
// Constructors
explicit CreateUserRequest() = default;
explicit CreateUserRequest(const QString &passwordHash, const QString &salt,
const QString &username, qint32 roleId)
: passwordHash_(passwordHash), roleId_(roleId), salt_(salt),
username_(username) {}
// Setters (inline methods)
inline void setUsername(const QString &username) { username_ = username; }
inline void setPasswordHash(const QString &passwordHash) {
passwordHash_ = passwordHash;
}
inline void setSalt(const QString &salt) { salt_ = salt; }
inline void setRoleId(qint32 roleId) { roleId_ = roleId; }
// Getters (inline methods)
inline QString username() const { return username_; }
inline QString passwordHash() const { return passwordHash_; }
inline QString salt() const { return salt_; }
inline qint32 roleId() const { return roleId_; }
};
#endif // CREATE_USER_REQUEST_H
+26
View File
@@ -0,0 +1,26 @@
#ifndef UPDATE_GENRE_REQUEST_H
#define UPDATE_GENRE_REQUEST_H
#include <QString>
class UpdateGenreRequest {
private:
QString genreName_; // Genre name
qint32 genreId_; // Genre ID
public:
// Constructors
explicit UpdateGenreRequest() = default;
explicit UpdateGenreRequest(const QString &genreName, qint32 genreId)
: genreName_(genreName), genreId_(genreId) {}
// Setters (inline methods)
inline void setGenreId(qint32 genreId) { genreId_ = genreId; }
inline void setGenreName(const QString &genreName) { genreName_ = genreName; }
// Getters (inline methods)
inline qint32 genreId() const { return genreId_; }
inline QString genreName() const { return genreName_; }
};
#endif // UPDATE_GENRE_REQUEST_H
+30
View File
@@ -0,0 +1,30 @@
#ifndef UPDATE_HALL_REQUEST_H
#define UPDATE_HALL_REQUEST_H
#include <QString>
class UpdateHallRequest {
private:
QString hallName_; // Hall name
qint32 capacity_; // Hall capacity
qint32 hallId_; // Hall ID
public:
// Constructors
explicit UpdateHallRequest() = default;
explicit UpdateHallRequest(const QString &hallName, qint32 capacity,
qint32 hallId)
: capacity_(capacity), hallId_(hallId), hallName_(hallName) {}
// Setters (inline methods)
inline void setHallId(qint32 hallId) { hallId_ = hallId; }
inline void setHallName(const QString &hallName) { hallName_ = hallName; }
inline void setCapacity(qint32 capacity) { capacity_ = capacity; }
// Getters (inline methods)
inline qint32 hallId() const { return hallId_; }
inline QString hallName() const { return hallName_; }
inline qint32 capacity() const { return capacity_; }
};
#endif // UPDATE_HALL_REQUEST_H
+35
View File
@@ -0,0 +1,35 @@
#ifndef UPDATE_MOVIE_REQUEST_H
#define UPDATE_MOVIE_REQUEST_H
#include <QString>
class UpdateMovieRequest {
private:
QString title_; // Movie title
qint32 duration_; // Movie duration
qint32 genreId_; // Genre ID
qint32 movieId_; // Movie ID
public:
// Constructors
explicit UpdateMovieRequest() = default;
explicit UpdateMovieRequest(qint32 movieId, const QString &title,
qint32 duration, qint32 genreId)
: movieId_(movieId), title_(title), duration_(duration),
genreId_(genreId) {}
// Setters (inline methods)
inline void setDuration(qint32 duration) { duration_ = duration; }
inline void setGenreId(qint32 genreId) { genreId_ = genreId; }
inline void setMovieId(qint32 movieId) { movieId_ = movieId; }
inline void setTitle(const QString &title) { title_ = title; }
// Getters (inline methods)
inline QString title() const { return title_; }
inline qint32 duration() const { return duration_; }
inline qint32 genreId() const { return genreId_; }
inline qint32 movieId() const { return movieId_; }
};
#endif // UPDATE_MOVIE_REQUEST_H
+37
View File
@@ -0,0 +1,37 @@
#ifndef UPDATE_REFUND_REQUEST_H
#define UPDATE_REFUND_REQUEST_H
#include <QDateTime>
class UpdateRefundRequest {
private:
QDateTime refundAt_; // Refund date and time
double refundAmount_; // Refund amount
qint32 refundId_; // Refund ID
qint32 ticketId_; // Ticket ID
public:
// Constructors
explicit UpdateRefundRequest() = default;
explicit UpdateRefundRequest(const QDateTime &refundAt, double refundAmount,
qint32 refundId, qint32 ticketId)
: refundAmount_(refundAmount), refundAt_(refundAt), refundId_(refundId),
ticketId_(ticketId) {}
// Setters (inline methods)
inline void setRefundAmount(double refundAmount) {
refundAmount_ = refundAmount;
}
inline void setRefundAt(const QDateTime &refundAt) { refundAt_ = refundAt; }
inline void setRefundId(qint32 refundId) { refundId_ = refundId; }
inline void setTicketId(qint32 ticketId) { ticketId_ = ticketId; }
// Getters (inline methods)
inline QDateTime refundAt() const { return refundAt_; }
inline double refundAmount() const { return refundAmount_; }
inline qint32 refundId() const { return refundId_; }
inline qint32 ticketId() const { return ticketId_; }
};
#endif // UPDATE_REFUND_REQUEST_H
+30
View File
@@ -0,0 +1,30 @@
#ifndef UPDATE_ROLE_REQUEST_H
#define UPDATE_ROLE_REQUEST_H
#include <QString>
class UpdateRoleRequest {
private:
QString roleName_; // Role name
qint32 accessLevel_; // Access level
qint32 roleId_; // Role ID
public:
// Constructors
explicit UpdateRoleRequest() = default;
explicit UpdateRoleRequest(const QString &roleName, qint32 accessLevel,
qint32 roleId)
: accessLevel_(accessLevel), roleId_(roleId), roleName_(roleName) {}
// Setters (inline methods)
inline void setRoleId(qint32 roleId) { roleId_ = roleId; }
inline void setRoleName(const QString &roleName) { roleName_ = roleName; }
inline void setAccessLevel(qint32 accessLevel) { accessLevel_ = accessLevel; }
// Getters (inline methods)
inline qint32 roleId() const { return roleId_; }
inline QString roleName() const { return roleName_; }
inline qint32 accessLevel() const { return accessLevel_; }
};
#endif // UPDATE_ROLE_REQUEST_H
+38
View File
@@ -0,0 +1,38 @@
#ifndef UPDATE_SESSION_REQUEST_H
#define UPDATE_SESSION_REQUEST_H
#include <QDateTime>
class UpdateSessionRequest {
private:
QDateTime beginAt_; // Session start time
double ticketPrice_; // Ticket price
qint32 hallId_; // Hall ID
qint32 movieId_; // Movie ID
qint32 sessionId_; // Session ID
public:
// Constructors
explicit UpdateSessionRequest() = default;
explicit UpdateSessionRequest(const QDateTime &beginAt, double ticketPrice,
qint32 hallId, qint32 movieId, qint32 sessionId)
: beginAt_(beginAt), hallId_(hallId), movieId_(movieId),
sessionId_(sessionId), ticketPrice_(ticketPrice) {}
// Setters (inline methods)
inline void setBeginAt(const QDateTime &beginAt) { beginAt_ = beginAt; }
inline void setHallId(qint32 hallId) { hallId_ = hallId; }
inline void setMovieId(qint32 movieId) { movieId_ = movieId; }
inline void setSessionId(qint32 sessionId) { sessionId_ = sessionId; }
inline void setTicketPrice(double ticketPrice) { ticketPrice_ = ticketPrice; }
// Getters (inline methods)
inline QDateTime beginAt() const { return beginAt_; }
inline double ticketPrice() const { return ticketPrice_; }
inline qint32 hallId() const { return hallId_; }
inline qint32 movieId() const { return movieId_; }
inline qint32 sessionId() const { return sessionId_; }
};
#endif // UPDATE_SESSION_REQUEST_H
+35
View File
@@ -0,0 +1,35 @@
#ifndef UPDATE_TICKET_REQUEST_H
#define UPDATE_TICKET_REQUEST_H
#include <QDateTime>
class UpdateTicketRequest {
private:
QDateTime soldAt_; // Ticket sold date and time
qint32 seatNumber_; // Seat number
qint32 sessionId_; // Session ID
qint32 ticketId_; // Ticket ID
public:
// Constructors
explicit UpdateTicketRequest() = default;
explicit UpdateTicketRequest(const QDateTime &soldAt, qint32 seatNumber,
qint32 sessionId, qint32 ticketId)
: seatNumber_(seatNumber), sessionId_(sessionId), soldAt_(soldAt),
ticketId_(ticketId) {}
// Setters (inline methods)
inline void setSeatNumber(qint32 seatNumber) { seatNumber_ = seatNumber; }
inline void setSessionId(qint32 sessionId) { sessionId_ = sessionId; }
inline void setSoldAt(const QDateTime &soldAt) { soldAt_ = soldAt; }
inline void setTicketId(qint32 ticketId) { ticketId_ = ticketId; }
// Getters (inline methods)
inline QDateTime soldAt() const { return soldAt_; }
inline qint32 seatNumber() const { return seatNumber_; }
inline qint32 sessionId() const { return sessionId_; }
inline qint32 ticketId() const { return ticketId_; }
};
#endif // UPDATE_TICKET_REQUEST_H
+40
View File
@@ -0,0 +1,40 @@
#ifndef UPDATE_USER_REQUEST_H
#define UPDATE_USER_REQUEST_H
#include <QString>
class UpdateUserRequest {
private:
QString passwordHash_; // Hashed password
QString salt_; // Salt for password hashing
QString username_; // Username
qint32 roleId_; // Role ID
qint32 userId_; // Unique user ID
public:
// Constructors
explicit UpdateUserRequest() = default;
explicit UpdateUserRequest(const QString &passwordHash, const QString &salt,
const QString &username, qint32 roleId,
qint32 userId)
: passwordHash_(passwordHash), roleId_(roleId), salt_(salt),
userId_(userId), username_(username) {}
// Setters (inline methods)
inline void setUserId(qint32 userId) { userId_ = userId; }
inline void setUsername(const QString &username) { username_ = username; }
inline void setPasswordHash(const QString &passwordHash) {
passwordHash_ = passwordHash;
}
inline void setSalt(const QString &salt) { salt_ = salt; }
inline void setRoleId(qint32 roleId) { roleId_ = roleId; }
// Getters (inline methods)
inline qint32 userId() const { return userId_; }
inline QString username() const { return username_; }
inline QString passwordHash() const { return passwordHash_; }
inline QString salt() const { return salt_; }
inline qint32 roleId() const { return roleId_; }
};
#endif // UPDATE_USER_REQUEST_H
+162
View File
@@ -0,0 +1,162 @@
#include "base_generator.h"
#include <QElapsedTimer>
#include <QVariant>
BaseGenerator::ShouldLogSuccess
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(' ')));
};
+72
View File
@@ -0,0 +1,72 @@
#ifndef BASE_GENERATOR_H
#define BASE_GENERATOR_H
#include "generator_interface.h"
#include "range_limiter.h"
#include <QDateTime>
#include <QRandomGenerator>
class BaseGenerator : public GeneratorInterface, public virtual RangeLimiter {
private:
QString entityName_;
int lastPercentage_;
mutable int fieldWidth_;
qsizetype createdRecords_;
qsizetype maximumRecords_;
public:
ShouldLogSuccess defaultShouldLog(int percent, int seconds) const noexcept;
ShouldLogSuccess shouldLogAllSuccesses() const noexcept;
ShouldLogSuccess shouldLogEveryNPercent(int percent) const noexcept;
ShouldLogSuccess shouldLogEveryNSeconds(
int seconds, const ShouldLogSuccess &performOtherwise) const noexcept;
ShouldLogSuccess shouldLogFirstAndLastPercent() const noexcept;
public:
// Virtual methods
void generateAll(const ShouldLogSuccess &shouldLogSuccess) final;
void generateByFillRatio(double fillRatio,
const ShouldLogSuccess &shouldLogSuccess) final;
void generateByRecordCount(qint64 totalRecordsToCreate,
const ShouldLogSuccess &shouldLogSuccess) final;
protected:
inline BaseGenerator(const QString &entityName) noexcept;
// General methods
inline QString formatDateTime(const QDateTime &dateTime_) const noexcept;
inline qsizetype createdRecords() const noexcept;
private: // create* methods depends on clearStaleData and createRecord
QString createFailureMessage(const QString &errorMessage) const override;
QString createSuccessMessage() const override;
QString createTemplateMessage() const override;
// Non-virtual method
void generateImplementation(std::function<bool()> shouldContinue,
std::function<bool()> shouldLogSuccess);
void updatePercentage(); // Depends on createRecord
protected: // Non-const methods
virtual RepositoryResult createRecord() = 0;
virtual bool prepareData() = 0;
virtual void clearStaleData() = 0;
};
BaseGenerator::BaseGenerator(const QString &entityName) noexcept
: createdRecords_(0), entityName_(entityName), fieldWidth_(0),
lastPercentage_(-1) {}
inline qsizetype BaseGenerator::createdRecords() const noexcept {
return createdRecords_;
}
QString
BaseGenerator::formatDateTime(const QDateTime &dateTime_) const noexcept {
return dateTime_.toString("yyyy-MM-dd'T'HH:mm:ss.000");
}
#endif
+75
View File
@@ -0,0 +1,75 @@
#include "date_time_generator.h"
DateTimeGenerator::DateTimeGenerator(const QPair<qint64, qint64> &dayRange,
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);
}
}
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)};
}
QDateTime DateTimeGenerator::generate(const QDateTime &lowerBoundInclusive,
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)};
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef DATE_TIME_GENERATOR_H
#define DATE_TIME_GENERATOR_H
#include "range_limiter.h"
#include <QDateTime>
class DateTimeGenerator : public virtual RangeLimiter {
private:
QPair<qint64, qint64> dayRange_;
QPair<QTime, QTime> timeRange_;
public:
DateTimeGenerator(const QPair<qint64, qint64> &dayRange,
const QPair<QTime, QTime> &timeRange);
QDateTime generate(const QDateTime &lowerBoundInclusive,
const QDateTime &upperBoundInclusive);
QDateTime generate(const QDateTime &upperBoundInclusive);
inline void setDayRange(const QPair<qint64, qint64> &dayRange);
inline void setTimeRange(const QPair<QTime, QTime> &timeRange);
inline void setUpperBoundInclusive(const QDateTime &upperBoundInclusive);
};
inline void
DateTimeGenerator::setDayRange(const QPair<qint64, qint64> &dayRange) {
dayRange_ = dayRange;
}
inline void
DateTimeGenerator::setTimeRange(const QPair<QTime, QTime> &timeRange) {
timeRange_ = timeRange;
}
#endif // DATE_TIME_GENERATOR_H
+29
View File
@@ -0,0 +1,29 @@
#ifndef GENERATOR_INTERFACE_H
#define GENERATOR_INTERFACE_H
#include "repository_result.h"
#include <QString>
class GeneratorInterface {
public:
using ShouldLogSuccess = std::function<bool()>;
public:
inline virtual ~GeneratorInterface() noexcept = default;
virtual void generateAll(const ShouldLogSuccess &shouldLogSuccess) = 0;
virtual void
generateByFillRatio(double fillRatio,
const ShouldLogSuccess &shouldLogSuccess) = 0;
virtual void
generateByRecordCount(qint64 totalRecordsToCreate,
const ShouldLogSuccess &shouldLogSuccess) = 0;
protected: // Const methods
virtual QString createFailureMessage(const QString &errorMessage) const = 0;
virtual QString createSuccessMessage() const = 0;
virtual QString createTemplateMessage() const = 0;
virtual qsizetype calculateRemainingCapacity() const = 0;
};
#endif
+95
View File
@@ -0,0 +1,95 @@
#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"
ModelManager::ModelManager() {
// Get connection to the database
database_ = QSqlDatabase::database();
if (!database_.isOpen()) {
qDebug() << "Database is not open. Cannot initialize models";
return;
}
initialize();
}
ModelManager::~ModelManager() {
qInfo() << "Models manager is being destroyed";
}
ModelManager &ModelManager::instance() {
static ModelManager instance;
return instance;
}
QStringList ModelManager::registeredModelTableNames() const {
return models_.keys();
}
QStringList
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;
}
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
}
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;
}
void ModelManager::resetFilter(const QString &modelTableName) {
BaseSqlTableModel *model = getModelAs<BaseSqlTableModel>(modelTableName);
model->resetFilter();
}
void ModelManager::setMultiColumnFilter(const QString &modelTableName,
const QString &filter) {
BaseSqlTableModel *model = getModelAs<BaseSqlTableModel>(modelTableName);
model->setMultiColumnFilter(filter);
}
+113
View File
@@ -0,0 +1,113 @@
#ifndef MODEL_MANAGER_H
#define MODEL_MANAGER_H
#include <QMap>
#include <QSqlDatabase>
#include <QSqlTableModel>
#include <QString>
// The model manager inherits the QObject class to delete child objects
class ModelManager : public QObject {
Q_OBJECT
private:
QMap<QString, QSqlTableModel *> models_;
QSqlDatabase database_;
public:
// Get the singleton instance
static ModelManager &instance();
QStringList registeredModelTableNames() const;
QStringList registeredModelTableNames(QString databaseTableName) const;
// Get a model
QSqlTableModel *getModel(const QString &modelTableName) const;
// Get a model with a specific type
template <typename T> T *getModelAs(const QString &modelTableName) const;
// Configure filters
void resetFilter(const QString &modelTableName);
void setMultiColumnFilter(const QString &modelTableName,
const QString &filter);
private:
// Register a model
template <typename T>
bool registerModel(const QString &modelTableName, const QSqlDatabase &db);
// Initialize all models
void initialize();
// Initialize a specific model
template <typename T> void initializeIfExists(T *model);
// Private constructor for singleton pattern
ModelManager();
// Private destructor
~ModelManager();
// Disable copy constructor and assignment operator
ModelManager(const ModelManager &) = delete;
ModelManager &operator=(const ModelManager &) = delete;
};
// NOTE: Implementation of template methods
template <typename T> void ModelManager::initializeIfExists(T *model) {
if constexpr (std::is_invocable_v<decltype(&T::initialize), T *>) {
model->initialize();
} else {
qDebug() << "Method initialize() is not available, skipping...";
}
}
template <typename T>
T *ModelManager::getModelAs(const QString &modelTableName) const {
auto *model = dynamic_cast<T *>(getModel(modelTableName));
if (!model) {
qFatal() << "Failed to downcast model for" << modelTableName
<< "(typeid name):" << typeid(T).name();
return nullptr;
}
qInfo() << "Successfully downcast model for" << modelTableName;
return model;
}
template <typename T>
bool ModelManager::registerModel(const QString &modelTableName,
const QSqlDatabase &db) {
static_assert(std::is_base_of_v<QSqlTableModel, T>,
"Registered model must be derived from QSqlTableModel");
static_assert(
std::is_constructible<T, QObject *, const QSqlDatabase &>::value,
"Model class must have a constructor accepting QObject* and const "
"QSqlDatabase&");
qInfo() << "Registering model:" << modelTableName;
if (models_.contains(modelTableName)) {
qInfo() << "Model" << modelTableName << "is already registered";
return true;
}
T *model = new T(this, db); // Qt will delete the model
model->setObjectName(modelTableName);
initializeIfExists(model);
models_.insert(modelTableName, model);
qInfo() << "Model" << modelTableName
<< "with database table name:" << model->tableName()
<< "was registered successfully";
return true;
}
#endif // MODEL_MANAGER_H
+34
View File
@@ -0,0 +1,34 @@
#ifndef RANGE_LIMITER_H
#define RANGE_LIMITER_H
#include <QDebug>
#include <QRandomGenerator>
class RangeLimiter {
private:
mutable QRandomGenerator gen_;
public:
inline RangeLimiter();
template <typename T> inline T bounded(T highest) const noexcept;
template <typename T> inline T bounded(T lowest, T highest) const noexcept;
};
RangeLimiter::RangeLimiter() : gen_(QRandomGenerator::securelySeeded()) {}
template <typename T> T RangeLimiter::bounded(T highest) const noexcept {
return gen_.bounded(highest);
}
template <typename T>
T RangeLimiter::bounded(T lowest, T highest) const noexcept {
if (lowest >= highest) {
QString errorMessage =
"Lowest value (%1) is greater than highest value (%2) or equal to it";
qFatal().noquote() << errorMessage.arg(lowest).arg(highest);
}
return gen_.bounded(lowest, highest);
}
#endif // RANGE_LIMITER_H
+168
View File
@@ -0,0 +1,168 @@
#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>
RefundGenerator::RefundGenerator(const QPair<double, double> &refundRatioRange,
const QPair<qint64, qint64> &dayRange,
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
}
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";
}
}
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
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);
}
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"));
}
qsizetype RefundGenerator::calculateRemainingCapacity() const {
return remainingCapacity_;
}
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;
}
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;
}
void RefundGenerator::clearStaleData() {
qint32 ticketId = reservationContext_.ticketId;
ticketIdToCacheValue_.remove(ticketId);
}
+76
View File
@@ -0,0 +1,76 @@
#ifndef REFUND_GENERATOR_H
#define REFUND_GENERATOR_H
#include "base_generator.h"
#include "date_time_generator.h"
#include <QDateTime>
#include <QRandomGenerator>
#include <QString>
// Forward declaration
class RefundsRepository;
class SessionsRepository;
class TicketsRepository;
class RefundGenerator : public BaseGenerator, public DateTimeGenerator {
private:
struct ReservationContext {
QDateTime beginAt;
QDateTime refundAt;
QDateTime soldAt;
double refundAmount;
qint32 ticketId;
};
struct CacheValue {
QDateTime beginAt;
QDateTime soldAt;
double ticketPrice;
};
private:
QMap<qint32 /* ticketId */, CacheValue> // WARNING: There can be a lot
ticketIdToCacheValue_; // of records, so 1 QPair is used, not 3 QMap
QPair<double, double> refundRatioRange_;
ReservationContext reservationContext_;
qsizetype remainingCapacity_;
RefundsRepository *refundsRepository_;
SessionsRepository *sessionsRepository_;
TicketsRepository *ticketsRepository_;
public:
RefundGenerator(const QPair<double, double> &refundRatioRange,
const QPair<qint64, qint64> &dayRange,
const QPair<QTime, QTime> &timeRange);
private:
void initializeRepositories();
void initializeTicketIdToCacheValueMap();
inline void initializeRemainingCapacity();
private:
inline qint64 calculateDurationMs(const CacheValue &cacheValue) const;
// Vitual methods
private: // Const methods
QString createFailureMessage(const QString &errorMessage) const override;
QString createSuccessMessage() const override;
qsizetype calculateRemainingCapacity() const override;
private: // Non-const methods
RepositoryResult createRecord() override;
bool prepareData() override;
void clearStaleData() override;
};
void RefundGenerator::initializeRemainingCapacity() {
remainingCapacity_ = ticketIdToCacheValue_.size();
}
qint64
RefundGenerator::calculateDurationMs(const CacheValue &cacheValue) const {
return cacheValue.soldAt.msecsTo(cacheValue.beginAt);
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#include "relation_id_config.h"
RelationIdConfig::RelationIdConfig(const QString &translentTableName,
const QString &modelTableName,
const QString &primaryIdColumnName,
const QString &repositoryTableName) noexcept
: translatedTableName_(translentTableName), modelTableName_(modelTableName),
primaryIdColumnName_(primaryIdColumnName),
repositoryTableName_(repositoryTableName) {}
+66
View File
@@ -0,0 +1,66 @@
#ifndef RALATION_ID_CONFIG_H
#define RALATION_ID_CONFIG_H
#include "QString"
class RelationIdConfig {
private:
QString modelTableName_;
QString primaryIdColumnName_;
QString repositoryTableName_;
QString translatedTableName_;
public:
RelationIdConfig() = default;
explicit RelationIdConfig(const QString &databaseModelName,
const QString &modelTableName,
const QString &primaryIdColumnName,
const QString &repositoryTableName) noexcept;
inline const QString &translatedTableName() const;
inline const QString &modelTableName() const;
inline const QString &primaryIdColumnName() const;
inline const QString &repositoryTableName() const;
inline void setTranslatedTableName(const QString &translatedTableName);
inline void setModelTableName(const QString &modelTableName);
inline void setPrimaryIdColumnName(const QString &primaryIdColumnName);
inline void setRepositoryTableName(const QString &repositoryTableName);
};
const QString &RelationIdConfig::translatedTableName() const {
return translatedTableName_;
}
const QString &RelationIdConfig::modelTableName() const {
return modelTableName_;
}
const QString &RelationIdConfig::primaryIdColumnName() const {
return primaryIdColumnName_;
}
const QString &RelationIdConfig::repositoryTableName() const {
return repositoryTableName_;
}
void RelationIdConfig::setTranslatedTableName(
const QString &translatedTableName) {
translatedTableName_ = translatedTableName;
}
void RelationIdConfig::setModelTableName(const QString &modelTableName) {
modelTableName_ = modelTableName;
}
void RelationIdConfig::setPrimaryIdColumnName(
const QString &primaryIdColumnName) {
primaryIdColumnName_ = primaryIdColumnName;
}
void RelationIdConfig::setRepositoryTableName(
const QString &repositoryTableName) {
repositoryTableName_ = repositoryTableName;
}
#endif // RALATION_ID_CONFIG_H
+83
View File
@@ -0,0 +1,83 @@
#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"
RepositoryManager::RepositoryManager() {
database_ = QSqlDatabase::database();
if (!database_.isOpen()) {
qDebug() << "Database is not open. Cannot initialize repositories";
return;
}
initialize();
}
RepositoryManager::~RepositoryManager() {
qInfo() << "Repositories manager is being destroyed";
}
RepositoryManager &RepositoryManager::instance() {
static RepositoryManager instance;
return instance;
}
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 *
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;
}
+90
View File
@@ -0,0 +1,90 @@
// repository_manager.h
#ifndef REPOSITORY_MANAGER_H
#define REPOSITORY_MANAGER_H
#include "repository_interface.h"
#include <QMap>
#include <QSqlDatabase>
// The repository manager inherits the QObject class to delete child objects
class RepositoryManager : public QObject {
Q_OBJECT
private:
// Database connection
QSqlDatabase database_;
// Map of repository names to instances
QMap<QString, RepositoryInterface *> repositories_;
public:
// Singleton instance accessor
static RepositoryManager &instance();
// Get a repository by name
RepositoryInterface *getRepository(const QString &repositoryTableName) const;
// Get a repository by name and type
template <typename T>
T *getRepositoryAs(const QString &repositoryTableName) const;
private:
// Register a new repository
template <typename T>
bool registerRepository(const QString &repositoryTableName);
// Initialize repositories
void initialize();
// Private constructor for singleton
RepositoryManager();
// Destructor to clean up resources
~RepositoryManager();
// Delete copy constructor
RepositoryManager(const RepositoryManager &) = delete;
// Delete assignment operator
RepositoryManager &operator=(const RepositoryManager &) = delete;
};
// NOTE: Implementation of template methods
template <typename T>
T *RepositoryManager::getRepositoryAs(
const QString &repositoryTableName) const {
auto *repository = dynamic_cast<T *>(getRepository(repositoryTableName));
if (!repository) {
qFatal() << "Failed to downcast repository for" << repositoryTableName
<< "(typeid name):" << typeid(T).name();
return nullptr;
}
qInfo() << "Successfully downcast repository for" << repositoryTableName;
return repository;
}
template <typename T>
bool RepositoryManager::registerRepository(const QString &repositoryTableName) {
static_assert(
std::is_constructible<T, QSqlDatabase &, QObject *>::value,
"Repository class must have a constructor accepting QSqlDatabase& and "
"QObject*");
if (repositories_.contains(repositoryTableName)) {
qInfo() << "Repository" << repositoryTableName << "is already registered";
return true;
}
auto *repository = new T(database_, this); // Qt will delete the model
repository->setObjectName(repositoryTableName);
repositories_.insert(repositoryTableName, repository);
qInfo() << "Repository" << repositoryTableName
<< "was registered successfully";
return true;
}
#endif // REPOSITORY_MANAGER_H
+137
View File
@@ -0,0 +1,137 @@
#include "schema_metadata.h"
#include <QDebug>
QMap<QString, RelationIdConfig> SchemaMetadata::databaseTableNameToConfigMap_;
std::once_flag SchemaMetadata::initFlag;
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";
}
SchemaMetadata::SchemaMetadata() noexcept {
std::call_once(initFlag, &SchemaMetadata::initialize);
}
SchemaMetadata::SchemaMetadata(const SchemaMetadata &) {}
SchemaMetadata &SchemaMetadata::operator=(const SchemaMetadata &) {
return *this;
}
SchemaMetadata SchemaMetadata::defaultSchema() { return SchemaMetadata(); }
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;
}
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;
}
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;
}
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
SchemaMetadata::relationIdConfig(const QString &databaseTableName) {
RelationIdConfig config =
databaseTableNameToConfigMap_.value(databaseTableName);
return config;
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef SCHEMA_METADATA_H
#define SCHEMA_METADATA_H
#include "relation_id_config.h"
#include <QMap>
#include <QObject>
#include <mutex>
class SchemaMetadata : QObject {
Q_OBJECT
private:
static QMap<QString, RelationIdConfig> databaseTableNameToConfigMap_;
static std::once_flag initFlag;
public:
SchemaMetadata &operator=(const SchemaMetadata &);
SchemaMetadata(const SchemaMetadata &);
QString modelTableName(const QString &databaseTableName);
QString primaryIdColumnName(const QString &databaseTableName);
QString repositoryTableName(const QString &databaseTableName);
QString translatedTableName(const QString &databaseTableName);
RelationIdConfig relationIdConfig(const QString &databaseTableName);
inline auto asKeyValueRange();
static SchemaMetadata defaultSchema();
private:
SchemaMetadata() noexcept;
static void initialize();
};
auto SchemaMetadata::asKeyValueRange() {
return databaseTableNameToConfigMap_.asKeyValueRange();
}
#endif // SCHEMA_METADATA_H
+254
View File
@@ -0,0 +1,254 @@
#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"
SessionGenerator::SessionGenerator(
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
}
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";
}
}
void SessionGenerator::initializeMovieIdToDurationMap(
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();
}
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
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();
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";
}
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);
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"));
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
// // 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(
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";
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;
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);
}
}
+72
View File
@@ -0,0 +1,72 @@
#ifndef SESSION_GENERATOR_H
#define SESSION_GENERATOR_H
#include "base_generator.h"
#include "reservation_manager.h"
#include <QDateTime>
#include <QMetaType>
#include <QRandomGenerator>
#include <QString>
#include <QVector>
// Forward declaration
class SessionsRepositoryInterface;
class HallsRepositoryInterface;
class MoviesRepositoryInterface;
class SessionGenerator : public BaseGenerator {
private:
struct ReservationContext {
QDateTime beginAt;
QTime endTime;
double ticketPrice;
qint32 hallId;
qint32 movieId;
};
private:
QMap<qint32 /* movieId */, qint32 /* minutes */> movieIdToDurationMinutes_;
QMap<qint32 /* hallId */, ReservationManager> hallIdToReservationManager_;
ReservationContext reservationContext_;
ReservationManager reservationReference_;
qint64 lowerBoundReservationMs_;
qint64 upperBoundReservationMs_;
// Repositories
HallsRepositoryInterface *hallsRepository_;
MoviesRepositoryInterface *moviesRepository_;
SessionsRepositoryInterface *sessionsRepository_;
public:
SessionGenerator(const ReservationManager &reservationReference = {});
// Non-virtual methods
private:
QTime calculateDuration(ReservationContext context) const;
QTime duration(qint32 movieId) const;
bool prepareData(qint32 hallId, const QList<qint32> &movieIds);
qsizetype calculateAvailableDaysCount() const;
qsizetype calculateAvailableSlotsCount() const;
void initializeBounds();
void initializeDayManager();
void initializeHallIdToReservationManagerMap();
void initializeMovieIdToDurationMap(qint32 additionalMinutes);
void initializeRepositories();
// Vitual methods
private: // Const methods
QString createFailureMessage(const QString &errorMessage) const override;
QString createSuccessMessage() const override;
qsizetype calculateRemainingCapacity() const override;
private: // Non-const methods
RepositoryResult createRecord() override;
bool prepareData() override;
void clearStaleData() override;
private:
friend class SessionGeneratorTest;
};
#endif // SESSION_GENERATOR_H
+219
View File
@@ -0,0 +1,219 @@
#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_;
std::mt19937 TicketGenerator::generator_(rd_());
TicketGenerator::TicketGenerator(
const QPair<qint32, qint32> &dayRange, const QPair<QTime, QTime> &timeRange,
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();
}
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";
}
}
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()];
}
}
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();
}
}
bool TicketGenerator::setFillRatioPerSessionRange(
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;
}
double TicketGenerator::generateFillRatioPerSession() const {
const auto &[min, max] = fillRatioPerSessionRange_;
std::uniform_real_distribution<double> distribution(min, max);
return distribution(generator_);
}
void TicketGenerator::initializeRemainingCapacity() {
remainingCapacity_ =
std::accumulate(sessionIdToAvailableSeatNumbers_.cbegin(),
sessionIdToAvailableSeatNumbers_.cend(), 0,
[&](qsizetype sum, const QSet<qint32> &availableSeats) {
return sum + availableSeats.size();
});
}
QString
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);
}
QString TicketGenerator::createSuccessMessage() const {
return QString("Success: EId: %1 SN: %2")
.arg(reservationContext_.sessionId)
.arg(reservationContext_.seatNumber);
}
qsizetype TicketGenerator::calculateRemainingCapacity() const {
return remainingCapacity_;
}
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;
}
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;
}
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);
}
}
+68
View File
@@ -0,0 +1,68 @@
#ifndef TICKET_GENERATOR_H
#define TICKET_GENERATOR_H
#include "base_generator.h"
#include "date_time_generator.h"
#include "tickets_repository_interface.h"
#include <QDateTime>
#include <QRandomGenerator>
#include <QString>
#include <QVector>
// Forward declaration
class HallsRepositoryInterface;
class SessionsRepositoryInterface;
class TicketsRepositoryInterface;
class TicketGenerator : public BaseGenerator, public DateTimeGenerator {
private:
struct ReservationContext {
QDateTime beginAt;
QDateTime soldAt;
qint32 seatNumber;
qint32 sessionId;
};
private:
static std::random_device rd_;
static std::mt19937 generator_;
private:
QMap<qint32 /* sessionId */, QDateTime> sessionIdToBeginAt_;
QMap<qint32 /* sessionId */, QSet<qint32> /* availableSeatNumbers */>
sessionIdToAvailableSeatNumbers_;
QPair<double, double> fillRatioPerSessionRange_;
ReservationContext reservationContext_;
qsizetype remainingCapacity_;
HallsRepositoryInterface *hallsRepository_;
SessionsRepositoryInterface *sessionsRepository_;
TicketsRepositoryInterface *ticketsRepository_;
public:
TicketGenerator(const QPair<qint32, qint32> &dayRange,
const QPair<QTime, QTime> &timeRange,
const QPair<double, double> &fillRatioPerSessionRange);
private:
void initializeRemainingCapacity();
void initializeRepositories();
void initializeSessionIdToAvailableSeatMap();
void initializeSessionIdToBeginAtMap();
bool setFillRatioPerSessionRange(const QPair<double, double> &range);
double generateFillRatioPerSession() const;
// Vitual methods
private: // Const methods
QString createFailureMessage(const QString &errorMessage) const override;
QString createSuccessMessage() const override;
qsizetype calculateRemainingCapacity() const override;
private: // Non-const methods
RepositoryResult createRecord() override;
bool prepareData() override;
void clearStaleData() override;
};
#endif // TICKET_GENERATOR_H
+32
View File
@@ -0,0 +1,32 @@
-- create indexes
-- genres
CREATE INDEX idx_genres_genre_name ON genres (genre_name);
-- movies
CREATE INDEX idx_movies_title ON movies (title);
CREATE INDEX idx_movies_genre_id ON movies (genre_id);
-- halls
CREATE INDEX idx_halls_hall_name ON halls (hall_name);
-- sessions
CREATE INDEX idx_sessions_movie_id ON sessions (movie_id);
CREATE INDEX idx_sessions_hall_id ON sessions (hall_id);
CREATE INDEX idx_sessions_begin_at ON sessions (begin_at);
CREATE INDEX idx_sessions_hall_id_begin_at ON sessions (hall_id, begin_at);
-- tickets
CREATE INDEX idx_tickets_session_id ON tickets (session_id);
CREATE INDEX idx_tickets_seat_number ON tickets (seat_number);
-- refunds
CREATE INDEX idx_refunds_ticket_id ON refunds (ticket_id);
-- roles
CREATE INDEX idx_roles_role_name ON roles (role_name);
CREATE INDEX idx_roles_access_level ON roles (access_level);
-- users
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_users_role_id ON users (role_id);
+83
View File
@@ -0,0 +1,83 @@
-- Create table "Genres" to store movie genres
CREATE TABLE genres (
genre_id INTEGER PRIMARY KEY AUTOINCREMENT,
genre_name TEXT NOT NULL UNIQUE CHECK(length(genre_name) > 0), -- Genre name cannot be empty
CONSTRAINT no_double_spaces_in_genre_name CHECK (genre_name NOT LIKE '% %') -- Genre name cannot contain double spaces
);
-- Create table "Movies" to store movie details
CREATE TABLE movies (
movie_id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL UNIQUE CHECK(length(title) > 0), -- Movie title cannot be empty
genre_id INTEGER,
duration INTEGER CHECK(duration > 0), -- Movie duration must be greater than 0
FOREIGN KEY (genre_id) REFERENCES genres (genre_id) ON DELETE CASCADE,
CONSTRAINT no_double_spaces_in_title CHECK (title NOT LIKE '% %') -- Movie title cannot contain double spaces
);
-- Create table "Halls" to store cinema hall details
CREATE TABLE halls (
hall_id INTEGER PRIMARY KEY AUTOINCREMENT,
hall_name TEXT NOT NULL UNIQUE CHECK(length(hall_name) > 0), -- Hall name cannot be empty
capacity INTEGER NOT NULL CHECK(capacity > 0), -- Hall capacity must be positive
CONSTRAINT no_double_spaces_in_hall_name CHECK (hall_name NOT LIKE '% %') -- Hall name cannot contain double spaces
);
-- Create table "Sessions" to store session details
CREATE TABLE sessions (
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
movie_id INTEGER,
hall_id INTEGER,
begin_at TIMESTAMP NOT NULL, -- Date and time of the session
ticket_price REAL NOT NULL CHECK(ticket_price >= 0), -- Ticket price cannot be negative
FOREIGN KEY (movie_id) REFERENCES movies (movie_id) ON DELETE CASCADE,
FOREIGN KEY (hall_id) REFERENCES halls (hall_id) ON DELETE CASCADE,
CONSTRAINT unique_hall_time UNIQUE (hall_id, begin_at) -- Prevent duplicate sessions in the same hall at the same time
);
-- Create table "Tickets" to store sold tickets
CREATE TABLE tickets (
ticket_id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id INTEGER,
seat_number INTEGER NOT NULL CHECK(seat_number > 0), -- Seat number must be positive
sold_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Sale time defaults to current time
FOREIGN KEY (session_id) REFERENCES sessions (session_id) ON DELETE CASCADE,
CONSTRAINT unique_session_seat UNIQUE(session_id, seat_number) -- Unique combination of session and seat number
);
-- Create table "Refunds" to store refunded tickets
CREATE TABLE refunds (
refund_id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL UNIQUE,
refund_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
refund_amount REAL NOT NULL CHECK (refund_amount >= 0), -- Refund amount cannot be negative
FOREIGN KEY (ticket_id) REFERENCES tickets(ticket_id) ON DELETE CASCADE
);
-- Create table "Roles" to store user roles
CREATE TABLE roles (
role_id INTEGER PRIMARY KEY AUTOINCREMENT,
role_name TEXT UNIQUE NOT NULL CHECK(length(role_name) > 0), -- Role name cannot be empty
access_level INTEGER UNIQUE NOT NULL CHECK(access_level > 0), -- Access level must be positive
CONSTRAINT no_spaces_in_role_name CHECK (role_name NOT LIKE '% %') -- Role name cannot contain spaces
);
-- Create table "Users" to store user details
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL CHECK(length(username) > 0), -- Username cannot be empty
password_hash TEXT NOT NULL CHECK(length(password_hash) > 0), -- Password hash cannot be empty
salt TEXT UNIQUE NOT NULL CHECK(length(salt) > 0), -- Salt cannot be empty
role_id INTEGER NOT NULL,
FOREIGN KEY (role_id) REFERENCES roles(role_id) ON DELETE CASCADE,
CONSTRAINT no_spaces_in_username CHECK (username NOT LIKE '% %') -- Username cannot contain spaces
);
+212
View File
@@ -0,0 +1,212 @@
-- WARNING: In SQLite DELIMITER doesn't work, so this file is handled in a special way
DELIMITER $$
-- INFO: ----- TICKET -----
-- Create trigger to validate seat number before inserting a ticket
-- INSERT
CREATE TRIGGER check_seat_number_before_insert
BEFORE INSERT ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions s
INNER JOIN halls h ON s.hall_id = h.hall_id
WHERE s.session_id = NEW.session_id
AND NEW.seat_number > h.capacity
)
BEGIN
SELECT RAISE(FAIL, 'Error 1001: Seat number exceeds hall capacity');
END$$
-- UPDATE
CREATE TRIGGER check_seat_number_before_update
BEFORE UPDATE ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions s
INNER JOIN halls h ON s.hall_id = h.hall_id
WHERE s.session_id = NEW.session_id
AND NEW.seat_number > h.capacity
)
BEGIN
SELECT RAISE(FAIL, 'Error 1001: Seat number exceeds hall capacity');
END$$
-- Check if the ticket purchase time is later than the session start time
-- INSERT
CREATE TRIGGER prevent_late_ticket_purchase_before_insert
BEFORE INSERT ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions
WHERE session_id = NEW.session_id
AND DATETIME(NEW.sold_at) >= DATETIME(begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1002: Cannot purchase ticket after the session has started');
END$$
-- UPDATE
CREATE TRIGGER prevent_late_ticket_purchase_before_update
BEFORE UPDATE ON tickets
WHEN EXISTS (
SELECT 1
FROM sessions
WHERE session_id = NEW.session_id
AND DATETIME(NEW.sold_at) >= DATETIME(begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1002: Cannot purchase ticket after the session has started');
END$$
-- INFO: ----- REFUND -----
-- Create trigger to prevent selling a ticket after the session has started
-- INSERT
CREATE TRIGGER prevent_late_refunds_before_insert
BEFORE INSERT ON refunds
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
AND t.ticket_id = NEW.ticket_id
WHERE DATETIME(NEW.refund_at) >= DATETIME(s.begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1003: Refunds cannot be made after the session has started');
END$$
-- UPDATE
CREATE TRIGGER prevent_late_refunds_before_update
BEFORE UPDATE ON refunds
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
AND t.ticket_id = NEW.ticket_id
WHERE DATETIME(NEW.refund_at) >= DATETIME(s.begin_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1003: Refunds cannot be made after the session has started');
END$$
-- Create a trigger to check if the refund amount is greater than the ticket sale amount
-- INSERT
CREATE TRIGGER validate_refund_amount_before_insert
BEFORE INSERT ON refunds
WHEN NEW.refund_amount > (
SELECT ticket_price
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
WHERE t.ticket_id = NEW.ticket_id
)
BEGIN
SELECT RAISE(FAIL, 'Error 1004: Refund amount cannot exceed the sale amount');
END$$
-- UPDATE
CREATE TRIGGER validate_refund_amount_before_update
BEFORE UPDATE ON refunds
WHEN NEW.refund_amount > (
SELECT ticket_price
FROM sessions s
JOIN tickets t ON s.session_id = t.session_id
WHERE t.ticket_id = NEW.ticket_id
)
BEGIN
SELECT RAISE(FAIL, 'Error 1004: Refund amount cannot exceed the sale amount');
END$$
-- Create a trigger to check if the refund date is earlier than the sale date
-- INSERT
CREATE TRIGGER validate_refund_date_before_insert
BEFORE INSERT ON refunds
WHEN EXISTS (
SELECT 1
FROM tickets
WHERE ticket_id = NEW.ticket_id
AND DATETIME(NEW.refund_at) <= DATETIME(sold_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1005: Refund date cannot be earlier than the sale date or equal to it');
END$$
-- UPDATE
CREATE TRIGGER validate_refund_date_before_update
BEFORE UPDATE ON refunds
WHEN EXISTS (
SELECT 1
FROM tickets
WHERE ticket_id = NEW.ticket_id
AND DATETIME(NEW.refund_at) <= DATETIME(sold_at)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1005: Refund date cannot be earlier than the sale date or equal to it');
END$$
-- INFO: ----- SESSION -----
-- Trigger to prevent overlapping sessions
--INSERT
CREATE TRIGGER prevent_overlapping_sessions_before_insert
BEFORE INSERT ON sessions
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN movies m_existing ON s.movie_id = m_existing.movie_id
JOIN movies m_new ON NEW.movie_id = m_new.movie_id
WHERE s.hall_id = NEW.hall_id
AND (
-- The new movie starts within an already occupied time slot
NEW.begin_at < DATETIME(s.begin_at, '+' || m_existing.duration || ' minutes')
-- Or the existing movie starts within the new movie's time slot
AND s.begin_at < DATETIME(NEW.begin_at, '+' || m_new.duration || ' minutes')
)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1006: The new session overlaps with an ongoing movie in the hall');
END$$
-- UPDATE
CREATE TRIGGER prevent_overlapping_sessions_before_update
BEFORE UPDATE ON sessions
WHEN EXISTS (
SELECT 1
FROM sessions s
JOIN movies m_existing ON s.movie_id = m_existing.movie_id
JOIN movies m_new ON NEW.movie_id = m_new.movie_id
WHERE s.hall_id = NEW.hall_id
AND (
-- The new movie starts within an already occupied time slot
NEW.begin_at < DATETIME(s.begin_at, '+' || m_existing.duration || ' minutes')
-- Or the existing movie starts within the new movie's time slot
AND s.begin_at < DATETIME(NEW.begin_at, '+' || m_new.duration || ' minutes')
)
)
BEGIN
SELECT RAISE(FAIL, 'Error 1006: The new session overlaps with an ongoing movie in the hall');
END$$
DELIMITER ;
+32
View File
@@ -0,0 +1,32 @@
CREATE VIEW sessions_statistics AS
SELECT
s.session_id,
s.begin_at,
s.ticket_price,
h.hall_id,
h.hall_name,
h.capacity AS total_seats,
m.movie_id,
m.title AS movie_title,
m.duration,
g.genre_id,
g.genre_name,
COUNT(t.ticket_id) AS sold_tickets,
COUNT(r.refund_id) AS refunded_tickets,
SUM(CASE WHEN t.ticket_id IS NOT NULL THEN s.ticket_price ELSE 0 END) AS sales_revenue,
SUM(CASE WHEN r.refund_id IS NOT NULL THEN r.refund_amount ELSE 0 END) AS refund_expenses
FROM halls h
JOIN sessions s
ON s.hall_id = h.hall_id
JOIN movies m
ON s.movie_id = m.movie_id
JOIN genres g
ON m.genre_id = g.genre_id
LEFT JOIN tickets t
ON t.session_id = s.session_id
LEFT JOIN refunds r
ON r.ticket_id = t.ticket_id
GROUP BY
s.session_id;
+3
View File
@@ -0,0 +1,3 @@
-- Disable foreign keys
-- Necessary testing behavior
PRAGMA foreign_keys = OFF;
+3
View File
@@ -0,0 +1,3 @@
-- Enable foreign keys
-- The necessary behavior for the normal operation of the program
PRAGMA foreign_keys = ON;
+23
View File
@@ -0,0 +1,23 @@
-- Insert data into the genres table
INSERT INTO genres (genre_id, genre_name) VALUES
(1, 'Боевик'),
(2, 'Комедия'),
(3, 'Драма'),
(4, 'Фантастика'),
(5, 'Ужасы'),
(6, 'Триллер'),
(7, 'Мелодрама'),
(8, 'Приключения'),
(9, 'Анимация'),
(10, 'Детектив'),
(11, 'Фэнтези'),
(12, 'Исторический'),
(13, 'Военный'),
(14, 'Музыкальный'),
(15, 'Семейный'),
(16, 'Спорт'),
(17, 'Документальный'),
(18, 'Криминал'),
(19, 'Биография'),
(20, 'Вестерн');
+7
View File
@@ -0,0 +1,7 @@
-- Insert data into the halls table
INSERT INTO halls (hall_name, capacity) VALUES ('Hall 1', 100);
INSERT INTO halls (hall_name, capacity) VALUES ('Hall 2', 150);
INSERT INTO halls (hall_name, capacity) VALUES ('VIP Hall', 50);
INSERT INTO halls (hall_name, capacity) VALUES ('IMAX Hall', 200);
+100
View File
@@ -0,0 +1,100 @@
-- Insert data into the movies table
INSERT INTO movies (title, genre_id, duration) VALUES
('Титаник', 3, 195),
('Матрица', 4, 136),
('Крепкий орешек', 1, 132),
('Один дома', 2, 103),
('Пила', 5, 103),
('Начало', 6, 148),
('Титаник 2', 3, 120),
('Звездные войны: Эпизод IV', 4, 121),
('Терминатор', 1, 107),
('Мальчишник в Вегасе', 2, 100),
('Звонок', 5, 115),
('Семь', 6, 127),
('Форрест Гамп', 3, 142),
('Аватар', 4, 162),
('Рокки', 1, 119),
('Маска', 2, 101),
('Оно', 5, 135),
('Молчание ягнят', 6, 118),
('Красота по-американски', 3, 122),
('Интерстеллар', 4, 169),
('Терминатор 2', 1, 137),
('День сурка', 2, 101),
('Пила 2', 5, 93),
('Игра', 6, 129),
('Список Шиндлера', 3, 195),
('Чужой', 4, 117),
('Рэмбо', 1, 93),
('Брюс Всемогущий', 2, 101),
('Заклятие', 5, 112),
('Шестое чувство', 6, 107),
('Гладиатор', 3, 155),
('Звездные войны: Эпизод V', 4, 124),
('Терминатор 3', 1, 109),
('Мальчишник 2', 2, 105),
('Пила 3', 5, 108),
('Остров проклятых', 6, 138),
('Властелин колец: Братство кольца', 11, 178),
('Властелин колец: Две крепости', 11, 179),
('Властелин колец: Возвращение короля', 11, 201),
('Гарри Поттер и философский камень', 11, 152),
('Гарри Поттер и Тайная комната', 11, 161),
('Гарри Поттер и узник Азкабана', 11, 142),
('Гарри Поттер и Кубок огня', 11, 157),
('Гарри Поттер и Орден Феникса', 11, 138),
('Гарри Поттер и Принц-полукровка', 11, 153),
('Гарри Поттер и Дары Смерти: Часть 1', 11, 146),
('Гарри Поттер и Дары Смерти: Часть 2', 11, 130),
('Пираты Карибского моря: Проклятие Черной жемчужины', 8, 143),
('Пираты Карибского моря: Сундук мертвеца', 8, 151),
('Пираты Карибского моря: На краю света', 8, 169),
('Пираты Карибского моря: На странных берегах', 8, 136),
('Пираты Карибского моря: Мертвецы не рассказывают сказки', 8, 129),
('Хоббит: Нежданное путешествие', 11, 169),
('Хоббит: Пустошь Смауга', 11, 161),
('Хоббит: Битва пяти воинств', 11, 144),
('Король Лев', 9, 88),
('Холодное сердце', 9, 102),
('Холодное сердце 2', 9, 103),
('Зверополис', 9, 108),
('История игрушек', 9, 81),
('История игрушек 2', 9, 92),
('История игрушек 3', 9, 103),
('История игрушек 4', 9, 100),
('В поисках Немо', 9, 100),
('В поисках Дори', 9, 97),
('Шрек', 9, 90),
('Шрек 2', 9, 93),
('Шрек Третий', 9, 93),
('Шрек навсегда', 9, 93),
('Кунг-фу Панда', 9, 92),
('Кунг-фу Панда 2', 9, 90),
('Кунг-фу Панда 3', 9, 95),
('Как приручить дракона', 9, 98),
('Как приручить дракона 2', 9, 102),
('Как приручить дракона 3', 9, 104),
('Рататуй', 9, 111),
('Головоломка', 9, 94),
('Суперсемейка', 9, 115),
('Суперсемейка 2', 9, 118),
('Тачки', 9, 117),
('Тачки 2', 9, 106),
('Тачки 3', 9, 102),
('Корпорация монстров', 9, 92),
('Университет монстров', 9, 104),
('Валл-И', 9, 98),
('Вверх', 9, 96),
('Храбрая сердцем', 9, 93),
('Ральф', 9, 101),
('Ральф против интернета', 9, 112),
('Моана', 9, 107),
('Зверопой', 9, 108),
('Зверопой 2', 9, 110),
('Коко', 9, 105),
('Душа', 9, 100),
('Лука', 9, 95),
('Райя и последний дракон', 9, 107),
('Энканто', 9, 102);
+9
View File
@@ -0,0 +1,9 @@
-- Insert data into the roles table
INSERT INTO roles (role_name, access_level) VALUES ('Cashier', 10);
INSERT INTO roles (role_name, access_level) VALUES ('SalesManager', 28);
INSERT INTO roles (role_name, access_level) VALUES ('DataEditor', 46);
INSERT INTO roles (role_name, access_level) VALUES ('RepertoireManager', 64);
INSERT INTO roles (role_name, access_level) VALUES ('DataAdministrator', 82);
INSERT INTO roles (role_name, access_level) VALUES ('SuperAdministrator', 100);
+40
View File
@@ -0,0 +1,40 @@
-- Insert users with different roles
-- Cashiers (role_id = 1), password: cashier (cashier1 & cashier2), cashier_alt (cashier3)
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('cashier1', '43f4fded07f17b903867b8293ad429b0c826e5d2c8ac5a66ed58cf3cc511ee60', 'random_salt_1', 1);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('cashier2', '2a2aebce4cc19cfd384377617e3008d42caeb0172970b64e8d39778995950cfc', 'random_salt_2', 1);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('cashier3', 'a474eac28b808053cf8fddd7d53412d710c1adeb218c0bd7b91add5760b16885', 'random_salt_3', 1);
-- Sales Managers (role_id = 2), password: sales_manager1, sales_manager2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('sales_manager1', '3ac5657c3cc5b44e291aed9f3dcf4881af3c6c53b49b31ef5ab0e35ef04d32dc', 'random_salt_4', 2);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('sales_manager2', '40ad3df805e5173265f65206b1a9fa1c04b7f2ebbd30d5c4bc8ae496c01e89c3', 'random_salt_5', 2);
-- Data Editors (role_id = 3), password: data_editor1, data_editor2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_editor1', '53a6ad3f69d1ba0f16fab96a295340a70aa6dd4524a06a9e281b458ec1bffdeb', 'random_salt_6', 3);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_editor2', '074423b2b32d87ea875905962f10cabe0623b75532323f7a6d1f29ff51a556e0', 'random_salt_7', 3);
-- Repertoire Managers (role_id = 4), password: repertoire_manager1, repertoire_manager2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('repertoire_manager1', 'd57d697fe8df03559cbd7cb6c15d29fc6584556ba92291400db64a788a788bd5', 'random_salt_8', 4);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('repertoire_manager2', '74a0a0bceb65454779c8acb2705df36a905355e10b96fa0360a023aaddbac2cf', 'random_salt_9', 4);
-- Data Administrators (role_id = 5), password: data_admin1, data_admin2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_admin1', '1b755d9430baa5f6db977e3ed87fc9d2d0b6e89e35c0f04529ec7484ca57ab54', 'random_salt_10', 5);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('data_admin2', '465a2368e915fc5bbc8f1ff56fec3bb5fda92088cb64285b4419507012677bf3', 'random_salt_11', 5);
-- Super Administrators (role_id = 6), password: super_admin1, super_admin2
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('super_admin1', 'b687b754afbbb073178a2b50f10dad03d0ff54019b771b1853c5ee3bfc6d93d2', 'random_salt_12', 6);
INSERT INTO users (username, password_hash, salt, role_id) VALUES
('super_admin2', 'cad48cc6068cfc8f164e16422f1c92e4cd52fa446d1ac2fa2a678406fa60c6b6', 'random_salt_13', 6);
+246
View File
@@ -0,0 +1,246 @@
#include "day_manager.h"
#include <QRandomGenerator>
#include <algorithm>
DayManager::DayManager(const QTime &begin, const QTime &end) : DayManager() {
addAvailableSlot(begin, end);
}
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);
}
}
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>
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...);
}
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;
}
QPair<QTime, QTime> DayManager::reserveAvailableSlotLeft(QTime begin,
qint64 requiredMs) {
QTime end = availableSlots_.value(begin);
QTime reservedEnd = begin.addMSecs(requiredMs);
if (reservedEnd < end) {
addAvailableSlot(reservedEnd, end);
}
availableSlots_.remove(begin);
return {begin, reservedEnd};
}
QPair<QTime, QTime> DayManager::reserveAvailableSlotRight(QTime begin,
qint64 requiredMs) {
QTime end = availableSlots_.value(begin);
availableSlots_.remove(begin);
QTime reservedBegin = end.addMSecs(-requiredMs);
if (reservedBegin > begin) {
addAvailableSlot(begin, reservedBegin);
}
return {reservedBegin, end};
}
QPair<QTime, QTime> DayManager::reserveAvailableSlotRandom(QTime begin,
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};
}
QPair<QTime, QTime> DayManager::reserveAvailableSlot(QTime begin,
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 {};
}
}
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);
}
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);
}
QPair<QTime, QTime> DayManager::reserveImplQuick(qint64 requiredMs) {
for (auto const &[begin, end] : availableSlots_.asKeyValueRange()) {
if (begin.msecsTo(end) >= requiredMs) {
return reserveAvailableSlot(begin, requiredMs);
}
}
return {};
}
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);
}
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 {};
}
}
void DayManager::setMinDuration(const QTime &duration) {
if (!duration.isValid()) {
qWarning() << "DayManager::setMinDuration: duration is invalid";
return;
}
minDurationMs_ = duration.msecsSinceStartOfDay();
}
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);
});
}
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);
}
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());
}
+75
View File
@@ -0,0 +1,75 @@
#ifndef DAY_MANAGER_H
#define DAY_MANAGER_H
#include <QTime>
class DayManager {
public:
enum class SlotSelectionMode { Maximal, Minimal, Quick, Random };
enum class StartTimeMode { Left, Random, Right };
private:
QMap<QTime, QTime> availableSlots_;
SlotSelectionMode slotSelectionMode_;
StartTimeMode startTimeMode_;
qint64 minDurationMs_;
qsizetype createdRecords_;
public:
inline DayManager();
explicit DayManager(const QTime &begin, const QTime &end);
QPair<QTime, QTime> reserve(const QTime &duration);
QTime duration() const;
QTime duration(const QTime &begin) const;
inline QTime minDuration() const noexcept;
inline qsizetype createdRecords() const noexcept;
inline qsizetype slotCount() const;
inline void setMode(SlotSelectionMode mode);
inline void setMode(StartTimeMode mode);
void addAvailableSlot(const QTime &begin, const QTime &end);
void setMinDuration(const QTime &duration);
qsizetype calculatePossibleReservations(qint64 requiredMs) const;
private:
template <typename... Times>
bool isAvailable(const QTime &first, const Times &...others) const;
QList<std::reference_wrapper<const QTime>>
availableSlots(qint64 requiredMs) const;
QPair<QTime, QTime> reserveAvailableSlot(QTime key, qint64 requiredMs);
QPair<QTime, QTime> reserveAvailableSlotLeft(QTime key, qint64 requiredMs);
QPair<QTime, QTime> reserveAvailableSlotRandom(QTime key, qint64 requiredMs);
QPair<QTime, QTime> reserveAvailableSlotRight(QTime key, qint64 requiredMs);
QPair<QTime, QTime> reserveImpl(qint64 requiredMs);
QPair<QTime, QTime> reserveImplMaximal(qint64 requiredMs);
QPair<QTime, QTime> reserveImplMinimal(qint64 requiredMs);
QPair<QTime, QTime> reserveImplQuick(qint64 requiredMs);
QPair<QTime, QTime> reserveImplRandom(qint64 requiredMs);
private:
friend class DayManagerTest;
};
DayManager::DayManager()
: slotSelectionMode_(SlotSelectionMode::Minimal),
startTimeMode_(StartTimeMode::Left), minDurationMs_(1),
createdRecords_(0) {}
void DayManager::setMode(SlotSelectionMode mode) { slotSelectionMode_ = mode; }
void DayManager::setMode(StartTimeMode mode) { startTimeMode_ = mode; }
inline QTime DayManager::minDuration() const noexcept {
return QTime::fromMSecsSinceStartOfDay(minDurationMs_);
}
qsizetype DayManager::createdRecords() const noexcept {
return createdRecords_;
}
qsizetype DayManager::slotCount() const { return availableSlots_.size(); }
#endif // DAY_MANAGER_H
+131
View File
@@ -0,0 +1,131 @@
#include "reservation_manager.h"
#include <QRandomGenerator>
ReservationManager::ReservationManager(qsizetype dayRange, qsizetype dayBound,
const DayManager &reference)
: createdRecords_(0), dayBound_(dayBound), dayRange_(dayRange),
excludedDays_(0), lastIndex_(-1), dayReference_(reference) {
prepareDaysRange();
}
void ReservationManager::setReference(const DayManager &reference) {
dayReference_ = reference;
days_.clear();
lastIndex_ = -1;
prepareDaysRange();
}
qsizetype ReservationManager::calculateAvailableDaysCount() const {
qsizetype bound =
dayBound_ != -1 ? dayBound_ : std::numeric_limits<qsizetype>::max();
return bound - excludedDays_;
}
qsizetype
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;
}
qsizetype ReservationManager::calculateAvailableSlotsCount() const {
return std::accumulate(days_.cbegin(), days_.cend(), 0,
[&](qsizetype sum, const DayManager manager) {
return sum + manager.slotCount();
});
}
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 {};
}
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");
}
QPair<QDateTime, QTime> ReservationManager::reserve(qsizetype dayIndex,
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)};
}
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_);
}
}
+62
View File
@@ -0,0 +1,62 @@
#ifndef RESERVATION_MANAGER_H
#define RESERVATION_MANAGER_H
#include "day_manager.h"
class ReservationManager {
private:
DayManager dayReference_;
QMap<qsizetype /* index */, DayManager> days_;
qsizetype createdRecords_;
qsizetype dayBound_;
qsizetype dayRange_;
qsizetype excludedDays_;
qsizetype lastIndex_;
public:
inline ReservationManager() noexcept;
explicit ReservationManager(qsizetype dayRange, qsizetype dayBound,
const DayManager &reference);
inline DayManager reference() const noexcept;
inline qsizetype createdRecords() const noexcept;
inline void setDayBound(qsizetype dayBound);
inline void setDayRange(qsizetype dayRange);
void setReference(const DayManager &manager);
qsizetype calculateAvailableDaysCount() const;
qsizetype calculatePossibleReservations(qint64 requiredMs) const;
qsizetype calculateAvailableSlotsCount() const;
QPair<QDateTime, QTime> reserve(const QTime &duration);
private:
DayManager &dayManager(qsizetype dayIndex);
QPair<QDateTime, QTime> reserve(qsizetype dayIndex, const QTime &duration);
void prepareDaysRange();
private:
friend class ReservationManagerTest;
};
ReservationManager::ReservationManager() noexcept
: createdRecords_(0), dayBound_(-1), excludedDays_(0), lastIndex_(-1),
dayReference_(DayManager()) {}
DayManager ReservationManager::reference() const noexcept {
return dayReference_;
}
inline qsizetype ReservationManager::createdRecords() const noexcept {
return createdRecords_;
}
void ReservationManager::setDayBound(qsizetype dayBound) {
dayBound_ = dayBound;
}
void ReservationManager::setDayRange(qsizetype dayRange) {
dayRange_ = dayRange;
}
#endif // RESERVATION_MANAGER_H