From 1086e360e5414024fc304cbbc7b22cb0260f25c3 Mon Sep 17 00:00:00 2001 From: Gerhard Hoffmann Date: Fri, 27 Sep 2024 14:04:39 +0200 Subject: [PATCH] Start of implementing new tariff calculator: Added serveral helper function for parsing tariff file: * getPrepaid() * getCarryOver() * getService() * getOutOfService() Added main functions of tariff calculator: * ComputeDurationFromCost() * ComputeCostFromDuration() --- library/src/calculator_functions.cpp | 424 +++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) diff --git a/library/src/calculator_functions.cpp b/library/src/calculator_functions.cpp index 9c8bd4d..005dd71 100644 --- a/library/src/calculator_functions.cpp +++ b/library/src/calculator_functions.cpp @@ -119,6 +119,430 @@ QDateTime Calculator::GetDailyTicketDuration(Configuration* cfg, const QDateTime return QDateTime(); } + +/// +/// \brief getPrepaid +/// \param cfg +/// \param dt +/// \return +/// + +std::optional getPrepaid(Configuration const *cfg, QDateTime const &dt) { + std::optional value = std::nullopt; + + int weekDay = dt.date().dayOfWeek(); + ATBTime inputTime(dt.time()); + auto const &prepaidRange = cfg->TariffPrepaids.equal_range(weekDay); + for (auto i = prepaidRange.first; i != prepaidRange.second; ++i) { + ATBTariffPrepaid const &prepaid = i->second; + TimeRange const &prepaidTimeRange = prepaid.m_range; + if (inputTime >= prepaidTimeRange.m_start && inputTime < prepaidTimeRange.m_end) { + value = value.value_or(i->second); + break; + } + } + + return value; +} + +/// +/// \brief getCarryOver +/// \param cfg +/// \param dt +/// \return +/// +std::optional getCarryOver(Configuration const *cfg, QDateTime const &dt) { + std::optional value = std::nullopt; + + int weekDay = dt.date().dayOfWeek(); + ATBTime inputTime(dt.time()); + auto const &carryOverRange = cfg->TariffCarryOvers.equal_range(weekDay); + for (auto i = carryOverRange.first; i != carryOverRange.second; ++i) { + ATBTariffCarryOver const &carryOver = i->second; + TimeRange const &carryOverTimeRange = carryOver.m_range; + if (inputTime >= carryOverTimeRange.m_start && inputTime < carryOverTimeRange.m_end) { + value = value.value_or(i->second); + break; + } + } + + return value; +} + +/// +/// \brief getService +/// \param cfg +/// \param dt +/// \return +/// + +std::optional getService(Configuration const *cfg, QDateTime const &dt) { + std::optional value = std::nullopt; + + int weekDay = dt.date().dayOfWeek(); + ATBTime inputTime(dt.time()); + auto const &serviceRange = cfg->TariffServices.equal_range(weekDay); + for (auto i = serviceRange.first; i != serviceRange.second; ++i) { + ATBTariffService const &service = i->second; + TimeRange const &serviceTimeRange = service.m_range; + if (inputTime >= serviceTimeRange.m_start && inputTime < serviceTimeRange.m_end) { + value = value.value_or(i->second); + break; + } + } + + return value; +} + +/// +/// \brief getOutOfService +/// \param cfg +/// \param dt +/// \return +/// +std::optional getOutOfService(Configuration const *cfg, QDateTime const &dt) { + std::optional value = std::nullopt; + + int weekDay = dt.date().dayOfWeek(); + ATBTime inputTime(dt.time()); + QDate date; + + auto const &outOfServiceRange = cfg->TariffOutOfServices.equal_range(weekDay); + for (auto i = outOfServiceRange.first; i != outOfServiceRange.second; ++i) { + ATBTariffOutOfService const &outOfService = i->second; + TimeRange const &outOfServiceTimeRange = outOfService.m_range; + if (outOfService.m_date == dt.date()) { + date = dt.date(); + if (inputTime >= outOfServiceTimeRange.m_start && inputTime < outOfServiceTimeRange.m_end) { + value = value.value_or(i->second); + return value; + } + } + } + + if (date.isNull() || !date.isValid()) { + for (auto i = outOfServiceRange.first; i != outOfServiceRange.second; ++i) { + ATBTariffOutOfService const &outOfService = i->second; + TimeRange const &outOfServiceTimeRange = outOfService.m_range; + if (inputTime >= outOfServiceTimeRange.m_start && inputTime < outOfServiceTimeRange.m_end) { + value = value.value_or(i->second); + return value; + } + } + } + + return value; +} + +std::pair +Calculator::ComputeDurationFromCost(Configuration const *cfg, + QDateTime const &startDatetimePassed, // given in local time + int cost) { + + QDateTime inputDate = startDatetimePassed; + inputDate.setTime(QTime(inputDate.time().hour(), inputDate.time().minute(), 0)); + + // TODO: + int paymentOptionIndex = 0; + + bool overPaid = false; + bool successMaxPrice = false; + + int const pop_id = cfg->getPaymentOptions(paymentOptionIndex).pop_id; + int const pop_accumulate_prices = cfg->getPaymentOptions(paymentOptionIndex).pop_accumulate_prices; + int const pop_max_price = cfg->getPaymentOptions(paymentOptionIndex).pop_max_price; + int const pop_max_time = cfg->getPaymentOptions(paymentOptionIndex).pop_max_time; + int const pop_min_price = cfg->getPaymentOptions(paymentOptionIndex).pop_min_price; + int const pop_allow_overpay = cfg->getPaymentOptions(paymentOptionIndex).pop_allow_overpay; + + int price = 0; + int durationId = 0; + int netto_parking_time_in_minutes = 0; + int brutto_parking_time_in_minutes = 0; + int free_parking_time_in_minutes = 0; + + QMap nettoParktimePrice; + QMap priceNettoParktime; + + for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) { + durationId = itr->second.pra_payment_unit_id; + + int const pra_price = itr->second.pra_price; + if (pop_accumulate_prices) { + price += pra_price; + } else { + price = pra_price; + } + + //if ((double)price == cost) { + auto search = cfg->Duration.find(durationId); + if (search != cfg->Duration.end()) { + // found now the duration in minutes + // check if we are still inside the working-time-range + ATBDuration duration = search->second; + nettoParktimePrice.insert(duration.pun_duration, price); + priceNettoParktime.insert(price, duration.pun_duration); + } + + + //} + } + + // qCritical() << __func__ << ":" << __LINE__ << nettoParktimePrice; + // qCritical() << __func__ << ":" << __LINE__ << priceNettoParktime; + + if (cost == pop_max_price) { + qCritical() << DBG_HEADER << "SUCCESS MAX-PARKING-PRICE" << pop_max_price << ", COST" << cost; + successMaxPrice = true; + } + + if (cost > pop_max_price) { + qCritical() << DBG_HEADER << "MAX-PARKING-PRICE" << pop_max_price << ", COST" << cost; + if (pop_allow_overpay == false) { + return std::make_pair(CalcState(CalcState::State::OVERPAID), QDateTime()); + } + cost = pop_max_price; + overPaid = true; + qCritical() << DBG_HEADER << "OVERPAID, MAX-PARKING-PRICE" << pop_max_price << ", COST" << cost; + // return CalcState::OVERPAID.toStdString(); + } + + if (cost < pop_min_price) { + qCritical() << DBG_HEADER << "MIN-PARKING-PRICE" << pop_min_price << ", COST" << cost; + return std::make_pair(CalcState(CalcState::State::BELOW_MIN_PARKING_PRICE), QDateTime()); + } + + int weekDay = inputDate.date().dayOfWeek(); + + qCritical() << __func__ << ":" << __LINE__ << "START weekDay" << weekDay << inputDate.toString(Qt::ISODate); + qCritical() << __func__ << ":" << __LINE__ << "START cost" << cost; + + QDateTime dt; + bool computationStarted = false; + price = 0; + netto_parking_time_in_minutes = 0; + brutto_parking_time_in_minutes = 0; + free_parking_time_in_minutes = 0; + + int const nettoParktimeForCost = priceNettoParktime[int(cost)]; + qCritical() << __func__ << ":" << __LINE__ << "nettoParktimeForCost" << nettoParktimeForCost; + + bool startDateNotOutOfService = false; + + int cnt = 0; + while (++cnt < 10 && netto_parking_time_in_minutes < nettoParktimeForCost) { + // qCritical() << __func__ << ":" << __LINE__ << "cnt [" << cnt; + + brutto_parking_time_in_minutes = free_parking_time_in_minutes + netto_parking_time_in_minutes; + dt = inputDate.addSecs(brutto_parking_time_in_minutes * 60); + weekDay = dt.date().dayOfWeek(); + + qCritical() << __func__ << ":" << __LINE__ << QString("%1 (%2): brutto: %3 = netto: %4 + free: %5") + .arg(dt.toString(Qt::ISODate)) + .arg(weekDay) + .arg(brutto_parking_time_in_minutes) + .arg(netto_parking_time_in_minutes) + .arg(free_parking_time_in_minutes); + + if (std::optional oos = getOutOfService(cfg, dt)) { + if (overPaid || startDateNotOutOfService) { + return std::make_pair(CalcState(CalcState::State::OVERPAID, + CalcState::OVERPAID), dt); + } + return std::make_pair(CalcState(CalcState::State::OUTSIDE_ALLOWED_PARKING_TIME, + CalcState::OUTSIDE_ALLOWED_PARKING_TIME), dt); + } else { + startDateNotOutOfService = true; + } + + if (computationStarted == false) { + computationStarted = true; + if (std::optional pp = getPrepaid(cfg, dt)) { + TimeRange const &prepaidTimeRange = pp.value().m_range; + ATBTime t(dt.time().hour(), dt.time().minute(), 0, 0); + free_parking_time_in_minutes += t.secsTo(prepaidTimeRange.m_end.toString(Qt::ISODate)) / 60; + } + } + + brutto_parking_time_in_minutes = free_parking_time_in_minutes + netto_parking_time_in_minutes; + dt = inputDate.addSecs(brutto_parking_time_in_minutes * 60); + weekDay = dt.date().dayOfWeek(); + + qCritical() << __func__ << ":" << __LINE__ << QString("%1 (%2): brutto: %3 = netto: %4 + free: %5") + .arg(dt.toString(Qt::ISODate)) + .arg(weekDay) + .arg(brutto_parking_time_in_minutes) + .arg(netto_parking_time_in_minutes) + .arg(free_parking_time_in_minutes); + + if (std::optional co = getCarryOver(cfg, inputDate.addSecs(brutto_parking_time_in_minutes * 60))) { + TimeRange const &carryOverTimeRange = co.value().m_range; + free_parking_time_in_minutes += carryOverTimeRange.m_duration; + } + + brutto_parking_time_in_minutes = free_parking_time_in_minutes + netto_parking_time_in_minutes; + dt = inputDate.addSecs(brutto_parking_time_in_minutes * 60); + weekDay = dt.date().dayOfWeek(); + + qCritical() << __func__ << ":" << __LINE__ << QString("%1 (%2): brutto: %3 = netto: %4 + free: %5") + .arg(dt.toString(Qt::ISODate)) + .arg(weekDay) + .arg(brutto_parking_time_in_minutes) + .arg(netto_parking_time_in_minutes) + .arg(free_parking_time_in_minutes); + + if (std::optional serv = getService(cfg, dt)) { + TimeRange const &serviceTimeRange = serv.value().m_range; + + if (nettoParktimeForCost > netto_parking_time_in_minutes) { + int rest_parking_time_in_minutes = nettoParktimeForCost - netto_parking_time_in_minutes; + ATBTime t(dt.time().hour(), dt.time().minute(), 0, 0); + int timeToServiceEnd = t.secsTo(serviceTimeRange.m_end.toString(Qt::ISODate)) / 60; + if (serviceTimeRange.m_duration > 0) { + if (timeToServiceEnd < rest_parking_time_in_minutes) { + netto_parking_time_in_minutes += timeToServiceEnd; + } else { + netto_parking_time_in_minutes += rest_parking_time_in_minutes; + } + } + } + } + + brutto_parking_time_in_minutes = free_parking_time_in_minutes + netto_parking_time_in_minutes; + dt = inputDate.addSecs(brutto_parking_time_in_minutes * 60); + weekDay = dt.date().dayOfWeek(); + + qCritical() << __func__ << ":" << __LINE__ << QString("%1 (%2): brutto: %3 = netto: %4 + free: %5") + .arg(dt.toString(Qt::ISODate)) + .arg(weekDay) + .arg(brutto_parking_time_in_minutes) + .arg(netto_parking_time_in_minutes) + .arg(free_parking_time_in_minutes); + + // qCritical() << __func__ << ":" << __LINE__ << "cnt" << cnt << "]"; + } + + if (cnt >= 10) { + qCritical() << __func__ << ":" << __LINE__ << "BREAK"; + } + + // configure if last carry-over ranges shall be added to ticket-end-time + cnt = 0; + while (std::optional co = getCarryOver(cfg, inputDate.addSecs(brutto_parking_time_in_minutes * 60))) { + if (++cnt > 5) { + qCritical() << __func__ << ":" << __LINE__ << "BREAK"; + break; + } + TimeRange const &carryOverTimeRange = co.value().m_range; + free_parking_time_in_minutes += carryOverTimeRange.m_duration; + brutto_parking_time_in_minutes = free_parking_time_in_minutes + netto_parking_time_in_minutes; + } + + brutto_parking_time_in_minutes = free_parking_time_in_minutes + netto_parking_time_in_minutes; + dt = inputDate.addSecs(brutto_parking_time_in_minutes * 60); + weekDay = dt.date().dayOfWeek(); + + qCritical() << __func__ << ":" << __LINE__ << QString("ticket-end-time %1 (%2): brutto: %3 = netto: %4 + free: %5") + .arg(dt.toString(Qt::ISODate)) + .arg(weekDay) + .arg(brutto_parking_time_in_minutes) + .arg(netto_parking_time_in_minutes) + .arg(free_parking_time_in_minutes); + + if (successMaxPrice) { + qCritical() << __func__ << ":" << __LINE__ << "SUCC" << dt; + return std::make_pair(CalcState(CalcState::State::SUCCESS_MAXPRICE), dt); + } + if (overPaid) { + qCritical() << __func__ << ":" << __LINE__ << "OVER" << dt; + return std::make_pair(CalcState(CalcState::State::OVERPAID), dt); + } + + qCritical() << __func__ << ":" << __LINE__ << "DT" << dt.toString(Qt::ISODate); + return std::make_pair(CalcState(CalcState::State::SUCCESS), dt); +} + +std::pair> +Calculator::ComputeCostFromDuration(Configuration const* cfg, QDateTime const &startDatetime, + QDateTime &endDatetime, int nettoParkingTime) { + + // TODO + int paymentOptionIndex = 0; + + std::optional cost{}; + + int const pop_id = cfg->getPaymentOptions(paymentOptionIndex).pop_id; + int const pop_accumulate_prices = cfg->getPaymentOptions(paymentOptionIndex).pop_accumulate_prices; + + int price = 0; + int durationId = 0; + int netto_parking_time_in_minutes = 0; + int brutto_parking_time_in_minutes = 0; + int free_parking_time_in_minutes = 0; + + QMap nettoParktimePrice; + QMap priceNettoParktime; + + for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) { + durationId = itr->second.pra_payment_unit_id; + + int const pra_price = itr->second.pra_price; + if (pop_accumulate_prices) { + price += pra_price; + } else { + price = pra_price; + } + + auto search = cfg->Duration.find(durationId); + if (search != cfg->Duration.end()) { + // found now the duration in minutes + // check if we are still inside the working-time-range + ATBDuration duration = search->second; + nettoParktimePrice.insert(duration.pun_duration, price); + priceNettoParktime.insert(price, duration.pun_duration); + } + } + + qCritical() << __func__ << ":" << __LINE__ << "START netto-parking-time" << nettoParkingTime; + CalcState returnState; + + QList keys = nettoParktimePrice.keys(); + int index = keys.indexOf(nettoParkingTime); + if (index != -1) { + int c = nettoParktimePrice[keys.at(index)]; + qCritical() << __func__ << ":" << __LINE__ << "cost for netto-parking-time" << c; + + std::pair r = ComputeDurationFromCost(cfg, startDatetime, c); + + qCritical() << __func__ << ":" << __LINE__ << "result" + << r.first.toString() << r.second.toString(Qt::ISODate); + + + returnState = r.first; + + if (returnState.getStatus() == CalcState::State::SUCCESS || + returnState.getStatus() == CalcState::State::SUCCESS_MAXPRICE) { + + endDatetime = r.second; + + qCritical() << __func__ << ":" << __LINE__ << "--- endDateTime" << endDatetime.toString(Qt::ISODate); + qCritical() << __func__ << ":" << __LINE__ << "------ r.second" << r.second.toString(Qt::ISODate); + + if (!endDatetime.isNull() && endDatetime.isValid()) { + cost = c; + } + } + } + + if (cost) { + qCritical() << __func__ << ":" << __LINE__ << "--- return cost" << cost.value(); + return std::make_pair(returnState, cost); + } + + qCritical() << __func__ << ":" << __LINE__ << "--- return error for cost" << returnState.toString(); + return std::make_pair(returnState, cost); +} + /// std::pair Calculator::GetDurationFromCost(Configuration* cfg,