220 lines
7.2 KiB
C++
220 lines
7.2 KiB
C++
|
|
#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);
|
||
|
|
}
|
||
|
|
}
|