#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 #include #include #include "repository_result.h" std::random_device TicketGenerator::rd_; std::mt19937 TicketGenerator::generator_(rd_()); TicketGenerator::TicketGenerator( const QPair &dayRange, const QPair &timeRange, const QPair &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( metadata.repositoryTableName("halls")); sessionsRepository_ = repositoryManager.getRepositoryAs( metadata.repositoryTableName("sessions")); ticketsRepository_ = repositoryManager.getRepositoryAs( metadata.repositoryTableName("tickets")); if (!bool(ticketsRepository_ && sessionsRepository_)) { qFatal() << "Failed to initialize repositories"; } } void TicketGenerator::initializeSessionIdToAvailableSeatMap() { QVector halls; RepositoryResult resultGetAllHalls = hallsRepository_->getAllHalls(halls); if (!resultGetAllHalls.success) { qFatal() << "Failed to retrieve halls"; } QVector sessions; RepositoryResult resultGetAllSessions = sessionsRepository_->getAllSessions(sessions); if (!resultGetAllSessions.success) { qFatal() << "Failed to retrieve sessions"; } QMap /* availableSeatNumbers */> hallIdToAvailableSeatNumbers; for (const HallDTO &hall : halls) { qint32 targetCapacity = std::ceil(hall.capacity() * generateFillRatioPerSession()); QVector availableSeats(targetCapacity); std::iota(availableSeats.begin(), availableSeats.end(), 1); std::shuffle(availableSeats.begin(), availableSeats.end(), generator_); QSet 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 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 &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 distribution(min, max); return distribution(generator_); } void TicketGenerator::initializeRemainingCapacity() { remainingCapacity_ = std::accumulate(sessionIdToAvailableSeatNumbers_.cbegin(), sessionIdToAvailableSeatNumbers_.cend(), 0, [&](qsizetype sum, const QSet &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(0, sessionIdToAvailableSeatNumbers_.size()); auto it = sessionIdToAvailableSeatNumbers_.begin(); std::advance(it, deltaIndex); const qint32 sessionId = it.key(); QSet &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(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 &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); } }