Initial commit
This commit is contained in:
@@ -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(' ')));
|
||||
};
|
||||
@@ -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
|
||||
@@ -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)};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) {}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user