Initial commit
This commit is contained in:
@@ -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 ¤tList : 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;
|
||||
};
|
||||
@@ -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> ¤tLink : 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 ¤tLink) -> 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> ¤tConnection :
|
||||
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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 ¤tStatement) {
|
||||
if (!filterLine(line)) {
|
||||
qInfo() << "Line is empty after filtering";
|
||||
return true; // Line was empty after filtering
|
||||
}
|
||||
|
||||
if (processDelimiterChange(line)) {
|
||||
qInfo() << "Delimiter changed to:" << delimiter_;
|
||||
return true;
|
||||
}
|
||||
|
||||
currentStatement += line + " ";
|
||||
|
||||
auto delimiterIndex = currentStatement.lastIndexOf(delimiter_);
|
||||
if (delimiterIndex == -1) {
|
||||
qInfo() << "Delimiter not found, current statement is not finished";
|
||||
return true;
|
||||
}
|
||||
qInfo() << "Statement finished, delimiter found at index:" << delimiterIndex;
|
||||
|
||||
// Execute the current statement
|
||||
QString completedStatement =
|
||||
currentStatement.replace(delimiter_, ";").trimmed();
|
||||
if (!executeStatement(completedStatement)) {
|
||||
qWarning() << "Failed to execute statement:" << completedStatement;
|
||||
return false; // Execution failed
|
||||
}
|
||||
qInfo() << "Successfully executed statement:" << completedStatement;
|
||||
|
||||
currentStatement.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 ¤tStatement);
|
||||
|
||||
// Processes a block of SQL statements, handling triggers separately
|
||||
bool processStatements(QStringList &statements);
|
||||
};
|
||||
|
||||
#endif // EXECUTE_SQL_FILE_H
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef REPOSITORY_INTERFACE_H
|
||||
#define REPOSITORY_INTERFACE_H
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
class RepositoryInterface {
|
||||
public:
|
||||
virtual ~RepositoryInterface() = default;
|
||||
};
|
||||
|
||||
#endif // REPOSITORY_INTERFACE_H
|
||||
@@ -0,0 +1,7 @@
|
||||
#include "repository_result.h"
|
||||
|
||||
RepositoryResult RepositoryResult::successResult() { return {"", true}; }
|
||||
|
||||
RepositoryResult RepositoryResult::failureResult(const QString &error) {
|
||||
return {error, false};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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 ;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Disable foreign keys
|
||||
-- Necessary testing behavior
|
||||
PRAGMA foreign_keys = OFF;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Enable foreign keys
|
||||
-- The necessary behavior for the normal operation of the program
|
||||
PRAGMA foreign_keys = ON;
|
||||
@@ -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, 'Вестерн');
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user