From d765997ca534bc618e7fa50b6cee772695cba44d Mon Sep 17 00:00:00 2001 From: Gerhard Hoffmann Date: Fri, 24 Nov 2023 13:52:49 +0100 Subject: [PATCH] dos2unix. add TODO comment --- library/src/calculator_functions.cpp | 1284 +++++++++++++------------- 1 file changed, 643 insertions(+), 641 deletions(-) diff --git a/library/src/calculator_functions.cpp b/library/src/calculator_functions.cpp index 7e505fc..763e8e0 100644 --- a/library/src/calculator_functions.cpp +++ b/library/src/calculator_functions.cpp @@ -1,641 +1,643 @@ -#include "calculator_functions.h" -#include "payment_option.h" -#include "utilities.h" -#include "tariff_log.h" -#include "tariff_time_range.h" - -#include -#include -#include -#include -#include - -double total_duration_min = 0.0f; -double total_cost = 0.0f; -bool overtime = false; - -#ifdef _WIN32 -inline struct tm* localtime_r(const time_t *clock, struct tm* result){ - if(!clock || !result) return NULL; - memcpy(result,localtime(clock),sizeof(*result)); - return result; -} -#endif - -QDateTime Calculator::GetDailyTicketDuration(Configuration* cfg, const QDateTime start_datetime, uint8_t payment_option, bool carry_over) -{ - if(!start_datetime.isValid()) { - return QDateTime(); - } - - double day_price = 0.0f; - int current_special_day_id = -1; - bool is_special_day = Utilities::CheckSpecialDay(cfg, start_datetime.toString(Qt::ISODate).toStdString().c_str(), ¤t_special_day_id, &day_price); - - QDateTime inputDateTime = start_datetime; - QTime worktime_from; - QTime worktime_to; - - int daily_card_price = cfg->PaymentOption.find(payment_option)->second.pop_daily_card_price; - if(daily_card_price <= 0) { - LOG_ERROR("Calculator::GetDailyTicketDuration(): Daily ticket price zero or less"); - return QDateTime(); - } - - if(is_special_day) - { - worktime_from = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_from.c_str(), Qt::ISODate); - worktime_to = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_to.c_str(),Qt::ISODate); - - if(inputDateTime.time() < worktime_from) inputDateTime.setTime(worktime_from); - if(carry_over) inputDateTime.setTime(worktime_from); - - if(inputDateTime.time() >= worktime_to) - { - // Go to next day if outside worktime - inputDateTime = inputDateTime.addSecs(86400); - return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); - } - - if(day_price <=0) - { - // Go to next day if special day price is 0 - inputDateTime = inputDateTime.addSecs(86400); - return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); - } - - int diff = abs(inputDateTime.time().secsTo(worktime_to)); - inputDateTime = inputDateTime.addSecs(diff); - - //qDebug() << "Ticket is valid until: " << inputDateTime.toString(Qt::ISODate) << "price = " << daily_card_price << ", duration = " << diff / 60; - return inputDateTime; - } - else - { - // Get day of week - int weekdayId = 0; - weekdayId = Utilities::ZellersAlgorithm(inputDateTime.date().day(),inputDateTime.date().month(),inputDateTime.date().year()); - - // If no working day found, skip it (recursively call method again) - size_t found = 0; - found = cfg->WeekDaysWorktime.count(weekdayId); - - // When no workday found, go to next available day - if(found <=0) - { - inputDateTime = inputDateTime.addSecs(86400); - return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); - } - else - { - worktime_from = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_from.c_str(),Qt::ISODate); - worktime_to = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_to.c_str(),Qt::ISODate); - if(inputDateTime.time() < worktime_from) - inputDateTime.setTime(worktime_from); - - if(carry_over) - inputDateTime.setTime(worktime_from); - - if(inputDateTime.time() >= worktime_to) - { - // Go to next day if outside worktime - inputDateTime = inputDateTime.addSecs(86400); - return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); - } - - int diff = abs(inputDateTime.time().secsTo(worktime_to)); - inputDateTime = inputDateTime.addSecs(diff); - - //qDebug() << "Ticket is valid until: " << inputDateTime.toString(Qt::ISODate) << "price = " << daily_card_price << ", duration = " << diff / 60; - return inputDateTime; - } - } - - return QDateTime(); -} -/// -std::string Calculator::GetDurationFromCost(Configuration* cfg, - uint8_t payment_option, - char const* start_datetime, // given in local time - double price, - bool nextDay, - bool prepaid) -{ - - // Get input date - QDateTime inputDate = QDateTime::fromString(start_datetime,Qt::ISODate); - - // use tariff with structure as for instance Schnau, Koenigsee: - // without given YearPeriod, SpecialDays and SpecialDaysWorktime - if (cfg->YearPeriod.size() == 0 - && cfg->SpecialDays.size() == 0 - && cfg->SpecialDaysWorktime.size() == 0) - { - inputDate = inputDate.addSecs(GetDurationForPrice(cfg, price) * 60); - - return inputDate.toString(Qt::ISODate).toStdString(); - } - - - // Get day of week - int weekdayId = 0; - weekdayId = Utilities::ZellersAlgorithm(inputDate.date().day(),inputDate.date().month(),inputDate.date().year()); - - //Get min and max time defined in JSON - double minMin = 0; - minMin = cfg->getPaymentOptions().pop_min_time; - - double maxMin = 0; - maxMin = cfg->getPaymentOptions().pop_max_time; - - double min_price = 0; - min_price = cfg->getPaymentOptions().pop_min_price; - - if(price < min_price) - { - return "PARKING NOT ALLOWED"; - } - - if (minMin < 0) minMin = 0; - if (maxMin < 0) maxMin = 0; - if (minMin >= maxMin) - { - LOG_ERROR("Error: min_min cannot be greater or equal to max_min"); - return "PARKING NOT ALLOWED"; - } - - if (maxMin <= minMin) - { - LOG_ERROR("Error: max_min cannot be lower or equal than min_min"); - return "PARKING NOT ALLOWED"; - } - - - // Get payment method - uint8_t p_method = PaymentMethod::Undefined; - p_method = payment_option; - LOG_DEBUG("Payment method id: ", (unsigned)p_method); - - // Check special day - double day_price = 0.0f; - int current_special_day_id = -1; - bool is_special_day = Utilities::CheckSpecialDay(cfg, inputDate.toString(Qt::ISODate).toStdString().c_str(), ¤t_special_day_id, &day_price); - LOG_DEBUG("Special day: ", is_special_day); - - double money_left = price; - double price_per_unit = 0.0f; - - QTime worktime_from; - QTime worktime_to; - - if(is_special_day) - { - // Set special day price - price_per_unit = Utilities::CalculatePricePerUnit(day_price); - worktime_from = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_from.c_str()); - worktime_to = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_to.c_str()); - } - else - { - // Set new price for the normal day - int pop_id = cfg->PaymentOption.find(payment_option)->second.pop_id; - day_price = cfg->PaymentRate.find(pop_id)->second.pra_price; - - int durationId = cfg->PaymentRate.find(pop_id)->second.pra_payment_unit_id; - double durationUnit = cfg->Duration.find(durationId)->second.pun_duration; - price_per_unit = Utilities::CalculatePricePerUnit(day_price,durationUnit); - - // If no working day found, skip it (recursively call method again) - size_t found = 0; - found = cfg->WeekDaysWorktime.count(weekdayId); - - // When no workday found, go to next available day - if(found <=0) - { - LOG_DEBUG("- No workday found, trying to find next available day"); - inputDate = inputDate.addDays(1); - return GetDurationFromCost(cfg, payment_option, inputDate.toString(Qt::ISODate).toStdString().c_str(), money_left,true,prepaid); - } - worktime_from = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_from.c_str()); - worktime_to = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_to.c_str()); - } - - if (price_per_unit < 0) price_per_unit = 1.0f; - - // if((price/price_per_unit) < minMin) return "PARKING NOT ALLOWED"; - LOG_DEBUG("Calculated price per minute: ", price_per_unit); - - // If overtime flag is set - if (overtime || nextDay) - { - inputDate.setTime(worktime_from); - overtime = false; - } - - // Check prepaid - if (!prepaid) - { - if ((inputDate.time() < worktime_from) || (inputDate.time() > worktime_to)) - { - LOG_DEBUG("[STOP] * Ticket is not valid * "); - return "PARKING NOT ALLOWED"; - } - } - else - { - LOG_DEBUG("* PREPAID MODE ACTIVE *"); - if (inputDate.time() < worktime_from) - { - inputDate.setTime(worktime_from); - } - else if(inputDate.time() > worktime_to) - { - LOG_DEBUG(" *** PREPAID *** Current time is past the time range end, searching for next available day"); - inputDate = inputDate.addDays(1); - return GetDurationFromCost(cfg, payment_option, inputDate.toString(Qt::ISODate).toStdString().c_str(), money_left, true); - } - } - - while(true) - { - if((int)money_left <= 0) break; - - // Check year period - bool isYearPeriodActive = false; - - //// Parse input date - int dayCurrent = inputDate.date().day(); - int monthCurrent = inputDate.date().month(); - - // Current date time - int cdt = (monthCurrent * 100) + dayCurrent; - - multimap::iterator year_period_itr; - for (year_period_itr = cfg->YearPeriod.begin(); year_period_itr != cfg->YearPeriod.end(); ++year_period_itr) - { - int dStart = year_period_itr->second.pye_start_day; - int dEnd = year_period_itr->second.pye_end_day; - - int mStart = year_period_itr->second.pye_start_month; - int mEnd = year_period_itr->second.pye_end_month; - - int start = (mStart * 100) + dStart; - int end = (mEnd * 100) + dEnd; - - if (cdt >= start && cdt <= end) { - isYearPeriodActive = true; - break; - } - } - if (!isYearPeriodActive) - { - LOG_DEBUG("Year period is not valid"); - return "PARKING NOT ALLOWED"; - } - - if(total_duration_min > maxMin) - { - total_duration_min = maxMin; - break; - } - - // If reached end of worktime go to next day - if(inputDate.time() >= worktime_to) - { - int carry_over_status = 0; - carry_over_status = cfg->PaymentOption.find(payment_option)->second.pop_carry_over; - if (carry_over_status < 1) break; - - inputDate = inputDate.addDays(1); - overtime = true; - return GetDurationFromCost(cfg, payment_option, inputDate.toString(Qt::ISODate).toStdString().c_str(), money_left ,true, prepaid); - } - - if(money_left > 1) - inputDate = inputDate.addSecs(60); - - if(price_per_unit > 0) total_duration_min +=1; - money_left -= price_per_unit; - - //qDebug() <<"Timestamp:" << inputDate << ", total duration min: " << total_duration_min << ", money left = " << money_left; - } - - // if ((total_duration_min < minMin) || (price / price_per_unit) < minMin) - // { - // LOG_DEBUG("Total duration is lower than min_min"); - // inputDate.time() = worktime_from; - // total_duration_min = 0; - // } - - double ret_val = 0; - // double calc_price = (int)total_duration_min - (int)price / price_per_unit; - - //if (calc_price > 0 && total_duration_min > 0) - //{ - // inputDate = inputDate.addSecs(-(int)ceil(calc_price) * 60); - //} - - if(price >= min_price && total_duration_min >= minMin) - qDebug() << "GetDurationFromCost(): Valid until: " << inputDate.toString(Qt::ISODate); - else - { - qDebug() << "Parking not allowed"; - total_duration_min = 0; - } - - ret_val = total_duration_min; - if(ret_val < 0) ret_val = 0; - qDebug() << "Duration: " << ret_val; - if (ret_val <= 0) return "PARKING NOT ALLOWED"; - - total_duration_min = 0; - return inputDate.toString(Qt::ISODate).toStdString(); -} - -/////////////////////////////////////// - -/// -/// - -uint32_t Calculator::GetCostFromDuration(Configuration *cfg, - QDateTime const &start, - quint64 timeStepInMinutes) const { - // for instance, a tariff as used in Schoenau, Koenigssee: only steps, no - // special days, nonstop. - if (cfg->YearPeriod.size() == 0 - && cfg->SpecialDays.size() == 0 - && cfg->SpecialDaysWorktime.size() == 0) { - QDateTime const end = start.addSecs(timeStepInMinutes*60); - return GetCostFromDuration(cfg, start, end); - } - return 0; -} - -uint32_t Calculator::GetCostFromDuration(Configuration * cfg, - QDateTime const &start, - QDateTime const &end) const { - if (cfg->YearPeriod.size() == 0 - && cfg->SpecialDays.size() == 0 - && cfg->SpecialDaysWorktime.size() == 0) { - int const timeStepInMinutes = start.secsTo(end) / 60; - return GetPriceForTimeStep(cfg, timeStepInMinutes); - } - return 0; -} - - - - -/////////////////////////////////////// - -/// -double Calculator::GetCostFromDuration(Configuration* cfg, - uint8_t payment_option, - const QDateTime start_datetime, - QDateTime &end_datetime, - int durationMinutes, - bool nextDay, - bool prepaid) -{ - if (cfg->YearPeriod.size() == 0 - && cfg->SpecialDays.size() == 0 - && cfg->SpecialDaysWorktime.size() == 0) - { - end_datetime = start_datetime.addSecs(durationMinutes*60); - return GetCostFromDuration(cfg, start_datetime, end_datetime); - } - - //Get min and max time defined in JSON - static int const minMin = std::max((int)cfg->PaymentOption.find(payment_option)->second.pop_min_time, 0); - static int const maxMin = std::max((int)cfg->PaymentOption.find(payment_option)->second.pop_max_time, 0); - - static const bool checkMinMaxMinutes = [](int minMin, int maxMin){ return (minMin < maxMin) ? true : false; }(minMin, maxMin); - - if (!checkMinMaxMinutes) { - qCritical() << QString("ERROR: CONDITION minMin < maxMin (%1 < %2) IS NOT VALID").arg(minMin).arg(maxMin); - return 0.0; - } - - // Get input date - QDateTime inputDate = start_datetime; - - // Get day of week - int const weekdayId = inputDate.date().dayOfWeek(); - - // weekdayId = Utilities::ZellersAlgorithm(inputDate.date().day(),inputDate.date().month(),inputDate.date().year()); - - // Check overtime - if (!overtime) { - if (durationMinutes > maxMin) { - qWarning() << QString("Total duration >= max_min (%1 >= %2)").arg(durationMinutes).arg(maxMin); - return maxMin; - } - - if (durationMinutes < minMin) { - qWarning() << QString("Total duration <= minMin (%1 <= %2)").arg(durationMinutes).arg(minMin); - return 0.0f; - } - } - - double day_price = 0.0; - int current_special_day_id = -1; - double price_per_unit = 0.0f; - - int const timeRanges = std::max((int)cfg->WeekDaysWorktime.count(weekdayId), 1); - QScopedArrayPointer worktime(new TariffTimeRange[timeRanges]); - int index = 0; - - if(Utilities::CheckSpecialDay(cfg, - inputDate.toString(Qt::ISODate).toStdString().c_str(), - ¤t_special_day_id, - &day_price)) { - // Set special day price - price_per_unit = Utilities::CalculatePricePerUnit(day_price); - worktime[index].setTimeRange(QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_from.c_str()), - QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_to.c_str())); - } else { - // Set new price for the normal day - - int pop_id = cfg->PaymentOption.find(payment_option)->second.pop_id; - day_price = cfg->PaymentRate.find(pop_id)->second.pra_price; - - int durationId = cfg->PaymentRate.find(pop_id)->second.pra_payment_unit_id; - double durationUnit = cfg->Duration.find(durationId)->second.pun_duration; - price_per_unit = Utilities::CalculatePricePerUnit(day_price,durationUnit); - - // If no working day found, skip it (recursively call method again) - if (cfg->WeekDaysWorktime.count(weekdayId) <= 0) { - // When no workday found, go to next available day - qDebug() << "No workday found, trying to find next available day"; - inputDate = inputDate.addDays(1); - return floor(GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes, true, prepaid)); - } - - for (auto[itr, rangeEnd] = cfg->WeekDaysWorktime.equal_range(weekdayId); itr != rangeEnd; ++itr) { - qCritical() << itr->first << itr->second.pwd_time_from.c_str() << itr->second.pwd_time_to.c_str(); - worktime[index].setTimeRange(QTime::fromString(itr->second.pwd_time_from.c_str()), - QTime::fromString(itr->second.pwd_time_to.c_str())); - index += 1; - } - } - - if (price_per_unit < 0) price_per_unit = 1.0f; - qDebug() << "Calculated price per minute=" << price_per_unit; - - double costFromDuration = 0.0; - for (int w = 0; w < index; ++w) { - QTime worktime_from = worktime[w].getTimeFrom(); - QTime worktime_to = worktime[w].getTimeUntil(); - - if (price_per_unit == 0) { - inputDate = inputDate.addDays(1); - inputDate.setTime(worktime_from); - double const partialCost = GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes, true, prepaid); - if (partialCost <= __DBL_MIN__) { - return 0.0; - } - costFromDuration += partialCost; - continue; - } - - // If overtime flag is set - if (overtime || nextDay) { - inputDate.setTime(worktime_from); - overtime = false; - } - - // Check prepaid - if (!prepaid) { - if ((inputDate.time() < worktime_from) || (inputDate.time() > worktime_to)) { - qDebug() << "[STOP] * Ticket is not valid * "; - return 0.0f; - } - } else { - qDebug() << "* PREPAID MODE ACTIVE *"; - if (inputDate.time() < worktime_from) { - inputDate.setTime(worktime_from); - } else if(inputDate.time() > worktime_to) { - qDebug() << " *** PREPAID *** Current time is past the time range end, searching for next available day"; - inputDate = inputDate.addDays(1); - double const partialCost = GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes, true, prepaid); - if (partialCost < __DBL_MIN__) { - return 0.0; - } - costFromDuration += partialCost; - continue; - } - } - - while(durationMinutes > 0) { - // Check for active year period - if (std::none_of(cfg->YearPeriod.begin(), - cfg->YearPeriod.end(), - [&inputDate](std::pair const &year) { - QDate const input(2004, // 2004 is a leap year - inputDate.date().month(), - inputDate.date().day()); - QDate const s(2004, year.second.pye_start_day, year.second.pye_start_month); - QDate const e(2004, year.second.pye_end_day, year.second.pye_end_month); - return (input >= s && input <= e); - })) { - qCritical() << "NO VALID YEAR PERIOD"; - return 0.0; - } - - // Go to next day if minutes not spent - if(inputDate.time() >= worktime_to) { - // check for carry_over status - if (cfg->PaymentOption.find(payment_option)->second.pop_carry_over < 1) { - break; - } - - qDebug() << "Reached end of worktime, searching for the next working day"; - - inputDate = inputDate.addDays(1); - overtime = true; - double const partialCost = GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes); - if (partialCost < __DBL_EPSILON__) { - return 0.0; - } - costFromDuration += partialCost; - break; // stop while, and continue in outer loop - } else { - // Increment input date minutes for each monetary unit - inputDate = inputDate.addSecs(60); - durationMinutes -= 1; - costFromDuration += price_per_unit; - } - } - } - - qDebug() << "GetCostFromDuration(): Valid until:" << inputDate.toString(Qt::ISODate); - - end_datetime = inputDate; - - //double ret_val = total_cost; - //total_cost = 0.0f; - //return ceil(ret_val); - - return ceil(costFromDuration); -} - - - -QList Calculator::GetTimeSteps(Configuration *cfg) const { - QList timeSteps; - - int const pop_id = cfg->getPaymentOptions().pop_id; - - for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) - { - int const durationId = itr->second.pra_payment_unit_id; - int const durationUnit = cfg->Duration.find(durationId)->second.pun_duration; - timeSteps << durationUnit; - } - - return timeSteps; -} - -uint32_t Calculator::GetPriceForTimeStep(Configuration *cfg, int timeStep) const { - - int const pop_id = cfg->getPaymentOptions().pop_id; - - for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) - { - int const payment_unit_id = itr->second.pra_payment_unit_id; - int const pun_id = cfg->Duration.find(payment_unit_id)->second.pun_id; - - Q_ASSERT(pun_id == payment_unit_id); - - int const pun_duration = cfg->Duration.find(payment_unit_id)->second.pun_duration; - if (timeStep == pun_duration) { - return (uint32_t)(itr->second.pra_price); - } - } - - return 0; -} - -uint32_t Calculator::GetDurationForPrice(Configuration *cfg, int price) const { - int const pop_id = cfg->getPaymentOptions().pop_id; - - uint32_t duration = 0; - - for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) - { - int const durationId = itr->second.pra_payment_unit_id; - int const pra_price = itr->second.pra_price; - - uint32_t const durationUnit = cfg->Duration.find(durationId)->second.pun_duration; - - if (pra_price == price) { - return durationUnit; - } - - if (pra_price < price) { - duration = durationUnit; - } - } - - return duration; -} +#include "calculator_functions.h" +#include "payment_option.h" +#include "utilities.h" +#include "tariff_log.h" +#include "tariff_time_range.h" + +#include +#include +#include +#include +#include + +double total_duration_min = 0.0f; +double total_cost = 0.0f; +bool overtime = false; + +#ifdef _WIN32 +inline struct tm* localtime_r(const time_t *clock, struct tm* result){ + if(!clock || !result) return NULL; + memcpy(result,localtime(clock),sizeof(*result)); + return result; +} +#endif + +QDateTime Calculator::GetDailyTicketDuration(Configuration* cfg, const QDateTime start_datetime, uint8_t payment_option, bool carry_over) +{ + if(!start_datetime.isValid()) { + return QDateTime(); + } + + double day_price = 0.0f; + int current_special_day_id = -1; + bool is_special_day = Utilities::CheckSpecialDay(cfg, start_datetime.toString(Qt::ISODate).toStdString().c_str(), ¤t_special_day_id, &day_price); + + QDateTime inputDateTime = start_datetime; + QTime worktime_from; + QTime worktime_to; + + int daily_card_price = cfg->PaymentOption.find(payment_option)->second.pop_daily_card_price; + if(daily_card_price <= 0) { + LOG_ERROR("Calculator::GetDailyTicketDuration(): Daily ticket price zero or less"); + return QDateTime(); + } + + if(is_special_day) + { + worktime_from = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_from.c_str(), Qt::ISODate); + worktime_to = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_to.c_str(),Qt::ISODate); + + if(inputDateTime.time() < worktime_from) inputDateTime.setTime(worktime_from); + if(carry_over) inputDateTime.setTime(worktime_from); + + if(inputDateTime.time() >= worktime_to) + { + // Go to next day if outside worktime + inputDateTime = inputDateTime.addSecs(86400); + return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); + } + + if(day_price <=0) + { + // Go to next day if special day price is 0 + inputDateTime = inputDateTime.addSecs(86400); + return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); + } + + int diff = abs(inputDateTime.time().secsTo(worktime_to)); + inputDateTime = inputDateTime.addSecs(diff); + + //qDebug() << "Ticket is valid until: " << inputDateTime.toString(Qt::ISODate) << "price = " << daily_card_price << ", duration = " << diff / 60; + return inputDateTime; + } + else + { + // Get day of week + int weekdayId = 0; + weekdayId = Utilities::ZellersAlgorithm(inputDateTime.date().day(),inputDateTime.date().month(),inputDateTime.date().year()); + + // If no working day found, skip it (recursively call method again) + size_t found = 0; + found = cfg->WeekDaysWorktime.count(weekdayId); + + // When no workday found, go to next available day + if(found <=0) + { + inputDateTime = inputDateTime.addSecs(86400); + return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); + } + else + { + worktime_from = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_from.c_str(),Qt::ISODate); + worktime_to = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_to.c_str(),Qt::ISODate); + if(inputDateTime.time() < worktime_from) + inputDateTime.setTime(worktime_from); + + if(carry_over) + inputDateTime.setTime(worktime_from); + + if(inputDateTime.time() >= worktime_to) + { + // Go to next day if outside worktime + inputDateTime = inputDateTime.addSecs(86400); + return GetDailyTicketDuration(cfg,inputDateTime, payment_option,true); + } + + int diff = abs(inputDateTime.time().secsTo(worktime_to)); + inputDateTime = inputDateTime.addSecs(diff); + + //qDebug() << "Ticket is valid until: " << inputDateTime.toString(Qt::ISODate) << "price = " << daily_card_price << ", duration = " << diff / 60; + return inputDateTime; + } + } + + return QDateTime(); +} +/// +std::string Calculator::GetDurationFromCost(Configuration* cfg, + uint8_t payment_option, + char const* start_datetime, // given in local time + double price, + bool nextDay, + bool prepaid) +{ + + // Get input date + QDateTime inputDate = QDateTime::fromString(start_datetime,Qt::ISODate); + + // use tariff with structure as for instance Schnau, Koenigsee: + // without given YearPeriod, SpecialDays and SpecialDaysWorktime + if (cfg->YearPeriod.size() == 0 + && cfg->SpecialDays.size() == 0 + && cfg->SpecialDaysWorktime.size() == 0) + { + inputDate = inputDate.addSecs(GetDurationForPrice(cfg, price) * 60); + + return inputDate.toString(Qt::ISODate).toStdString(); + } + + + // Get day of week + int weekdayId = 0; + weekdayId = Utilities::ZellersAlgorithm(inputDate.date().day(),inputDate.date().month(),inputDate.date().year()); + + //Get min and max time defined in JSON + double minMin = 0; + minMin = cfg->getPaymentOptions().pop_min_time; + + double maxMin = 0; + maxMin = cfg->getPaymentOptions().pop_max_time; + + double min_price = 0; + min_price = cfg->getPaymentOptions().pop_min_price; + + if(price < min_price) + { + return "PARKING NOT ALLOWED"; + } + + if (minMin < 0) minMin = 0; + if (maxMin < 0) maxMin = 0; + if (minMin >= maxMin) + { + LOG_ERROR("Error: min_min cannot be greater or equal to max_min"); + return "PARKING NOT ALLOWED"; + } + + if (maxMin <= minMin) + { + LOG_ERROR("Error: max_min cannot be lower or equal than min_min"); + return "PARKING NOT ALLOWED"; + } + + + // Get payment method + uint8_t p_method = PaymentMethod::Undefined; + p_method = payment_option; + LOG_DEBUG("Payment method id: ", (unsigned)p_method); + + // Check special day + double day_price = 0.0f; + int current_special_day_id = -1; + bool is_special_day = Utilities::CheckSpecialDay(cfg, inputDate.toString(Qt::ISODate).toStdString().c_str(), ¤t_special_day_id, &day_price); + LOG_DEBUG("Special day: ", is_special_day); + + double money_left = price; + double price_per_unit = 0.0f; + + QTime worktime_from; + QTime worktime_to; + + if(is_special_day) + { + // Set special day price + price_per_unit = Utilities::CalculatePricePerUnit(day_price); + worktime_from = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_from.c_str()); + worktime_to = QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_to.c_str()); + } + else + { + // Set new price for the normal day + int pop_id = cfg->PaymentOption.find(payment_option)->second.pop_id; + day_price = cfg->PaymentRate.find(pop_id)->second.pra_price; + + int durationId = cfg->PaymentRate.find(pop_id)->second.pra_payment_unit_id; + double durationUnit = cfg->Duration.find(durationId)->second.pun_duration; + price_per_unit = Utilities::CalculatePricePerUnit(day_price,durationUnit); + + // If no working day found, skip it (recursively call method again) + size_t found = 0; + found = cfg->WeekDaysWorktime.count(weekdayId); + + // When no workday found, go to next available day + if(found <=0) + { + LOG_DEBUG("- No workday found, trying to find next available day"); + inputDate = inputDate.addDays(1); + return GetDurationFromCost(cfg, payment_option, inputDate.toString(Qt::ISODate).toStdString().c_str(), money_left,true,prepaid); + } + worktime_from = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_from.c_str()); + worktime_to = QTime::fromString(cfg->WeekDaysWorktime.find(weekdayId)->second.pwd_time_to.c_str()); + } + + if (price_per_unit < 0) price_per_unit = 1.0f; + + // if((price/price_per_unit) < minMin) return "PARKING NOT ALLOWED"; + LOG_DEBUG("Calculated price per minute: ", price_per_unit); + + // If overtime flag is set + if (overtime || nextDay) + { + inputDate.setTime(worktime_from); + overtime = false; + } + + // Check prepaid + if (!prepaid) + { + if ((inputDate.time() < worktime_from) || (inputDate.time() > worktime_to)) + { + LOG_DEBUG("[STOP] * Ticket is not valid * "); + return "PARKING NOT ALLOWED"; + } + } + else + { + LOG_DEBUG("* PREPAID MODE ACTIVE *"); + if (inputDate.time() < worktime_from) + { + inputDate.setTime(worktime_from); + } + else if(inputDate.time() > worktime_to) + { + LOG_DEBUG(" *** PREPAID *** Current time is past the time range end, searching for next available day"); + inputDate = inputDate.addDays(1); + return GetDurationFromCost(cfg, payment_option, inputDate.toString(Qt::ISODate).toStdString().c_str(), money_left, true); + } + } + + while(true) + { + if((int)money_left <= 0) break; + + // Check year period + bool isYearPeriodActive = false; + + //// Parse input date + int dayCurrent = inputDate.date().day(); + int monthCurrent = inputDate.date().month(); + + // Current date time + int cdt = (monthCurrent * 100) + dayCurrent; + + multimap::iterator year_period_itr; + for (year_period_itr = cfg->YearPeriod.begin(); year_period_itr != cfg->YearPeriod.end(); ++year_period_itr) + { + int dStart = year_period_itr->second.pye_start_day; + int dEnd = year_period_itr->second.pye_end_day; + + int mStart = year_period_itr->second.pye_start_month; + int mEnd = year_period_itr->second.pye_end_month; + + int start = (mStart * 100) + dStart; + int end = (mEnd * 100) + dEnd; + + if (cdt >= start && cdt <= end) { + isYearPeriodActive = true; + break; + } + } + if (!isYearPeriodActive) + { + LOG_DEBUG("Year period is not valid"); + return "PARKING NOT ALLOWED"; + } + + if(total_duration_min > maxMin) + { + total_duration_min = maxMin; + break; + } + + // If reached end of worktime go to next day + if(inputDate.time() >= worktime_to) + { + int carry_over_status = 0; + carry_over_status = cfg->PaymentOption.find(payment_option)->second.pop_carry_over; + if (carry_over_status < 1) break; + + inputDate = inputDate.addDays(1); + overtime = true; + return GetDurationFromCost(cfg, payment_option, inputDate.toString(Qt::ISODate).toStdString().c_str(), money_left ,true, prepaid); + } + + if(money_left > 1) + inputDate = inputDate.addSecs(60); + + if(price_per_unit > 0) total_duration_min +=1; + money_left -= price_per_unit; + + //qDebug() <<"Timestamp:" << inputDate << ", total duration min: " << total_duration_min << ", money left = " << money_left; + } + + // if ((total_duration_min < minMin) || (price / price_per_unit) < minMin) + // { + // LOG_DEBUG("Total duration is lower than min_min"); + // inputDate.time() = worktime_from; + // total_duration_min = 0; + // } + + double ret_val = 0; + // double calc_price = (int)total_duration_min - (int)price / price_per_unit; + + //if (calc_price > 0 && total_duration_min > 0) + //{ + // inputDate = inputDate.addSecs(-(int)ceil(calc_price) * 60); + //} + + if(price >= min_price && total_duration_min >= minMin) + qDebug() << "GetDurationFromCost(): Valid until: " << inputDate.toString(Qt::ISODate); + else + { + qDebug() << "Parking not allowed"; + total_duration_min = 0; + } + + ret_val = total_duration_min; + if(ret_val < 0) ret_val = 0; + qDebug() << "Duration: " << ret_val; + if (ret_val <= 0) return "PARKING NOT ALLOWED"; + + total_duration_min = 0; + return inputDate.toString(Qt::ISODate).toStdString(); +} + +/////////////////////////////////////// + +/// +/// + +uint32_t Calculator::GetCostFromDuration(Configuration *cfg, + QDateTime const &start, + quint64 timeStepInMinutes) const { + // for instance, a tariff as used in Schoenau, Koenigssee: only steps, no + // special days, nonstop. + if (cfg->YearPeriod.size() == 0 + && cfg->SpecialDays.size() == 0 + && cfg->SpecialDaysWorktime.size() == 0) { + QDateTime const end = start.addSecs(timeStepInMinutes*60); + return GetCostFromDuration(cfg, start, end); + } + return 0; +} + +uint32_t Calculator::GetCostFromDuration(Configuration * cfg, + QDateTime const &start, + QDateTime const &end) const { + if (cfg->YearPeriod.size() == 0 + && cfg->SpecialDays.size() == 0 + && cfg->SpecialDaysWorktime.size() == 0) { + int const timeStepInMinutes = start.secsTo(end) / 60; + return GetPriceForTimeStep(cfg, timeStepInMinutes); + } + return 0; +} + + + + +/////////////////////////////////////// + +/// +double Calculator::GetCostFromDuration(Configuration* cfg, + uint8_t payment_option, + const QDateTime start_datetime, + QDateTime &end_datetime, + int durationMinutes, + bool nextDay, + bool prepaid) +{ + if (cfg->YearPeriod.size() == 0 + && cfg->SpecialDays.size() == 0 + && cfg->SpecialDaysWorktime.size() == 0) + { + end_datetime = start_datetime.addSecs(durationMinutes*60); + return GetCostFromDuration(cfg, start_datetime, end_datetime); + } + + //Get min and max time defined in JSON + static int const minMin = std::max((int)cfg->PaymentOption.find(payment_option)->second.pop_min_time, 0); + static int const maxMin = std::max((int)cfg->PaymentOption.find(payment_option)->second.pop_max_time, 0); + + static const bool checkMinMaxMinutes = [](int minMin, int maxMin){ return (minMin < maxMin) ? true : false; }(minMin, maxMin); + + if (!checkMinMaxMinutes) { + qCritical() << QString("ERROR: CONDITION minMin < maxMin (%1 < %2) IS NOT VALID").arg(minMin).arg(maxMin); + return 0.0; + } + + // Get input date + QDateTime inputDate = start_datetime; + + // Get day of week + int const weekdayId = inputDate.date().dayOfWeek(); + + // weekdayId = Utilities::ZellersAlgorithm(inputDate.date().day(),inputDate.date().month(),inputDate.date().year()); + + // Check overtime + if (!overtime) { + if (durationMinutes > maxMin) { + qWarning() << QString("Total duration >= max_min (%1 >= %2)").arg(durationMinutes).arg(maxMin); + return maxMin; + } + + if (durationMinutes < minMin) { + qWarning() << QString("Total duration <= minMin (%1 <= %2)").arg(durationMinutes).arg(minMin); + return 0.0f; + } + } + + double day_price = 0.0; + int current_special_day_id = -1; + double price_per_unit = 0.0f; + + int const timeRanges = std::max((int)cfg->WeekDaysWorktime.count(weekdayId), 1); + QScopedArrayPointer worktime(new TariffTimeRange[timeRanges]); + int index = 0; + + if(Utilities::CheckSpecialDay(cfg, + inputDate.toString(Qt::ISODate).toStdString().c_str(), + ¤t_special_day_id, + &day_price)) { + // Set special day price + price_per_unit = Utilities::CalculatePricePerUnit(day_price); + worktime[index].setTimeRange(QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_from.c_str()), + QTime::fromString(cfg->SpecialDaysWorktime.find(current_special_day_id)->second.pedwt_time_to.c_str())); + } else { + // Set new price for the normal day + + int pop_id = cfg->PaymentOption.find(payment_option)->second.pop_id; + day_price = cfg->PaymentRate.find(pop_id)->second.pra_price; + + int durationId = cfg->PaymentRate.find(pop_id)->second.pra_payment_unit_id; + double durationUnit = cfg->Duration.find(durationId)->second.pun_duration; + price_per_unit = Utilities::CalculatePricePerUnit(day_price,durationUnit); + + // If no working day found, skip it (recursively call method again) + if (cfg->WeekDaysWorktime.count(weekdayId) <= 0) { + // When no workday found, go to next available day + qDebug() << "No workday found, trying to find next available day"; + inputDate = inputDate.addDays(1); + return floor(GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes, true, prepaid)); + } + + for (auto[itr, rangeEnd] = cfg->WeekDaysWorktime.equal_range(weekdayId); itr != rangeEnd; ++itr) { + qCritical() << itr->first << itr->second.pwd_time_from.c_str() << itr->second.pwd_time_to.c_str(); + worktime[index].setTimeRange(QTime::fromString(itr->second.pwd_time_from.c_str()), + QTime::fromString(itr->second.pwd_time_to.c_str())); + index += 1; + } + } + + if (price_per_unit < 0) price_per_unit = 1.0f; + qDebug() << "Calculated price per minute=" << price_per_unit; + + double costFromDuration = 0.0; + for (int w = 0; w < index; ++w) { + QTime worktime_from = worktime[w].getTimeFrom(); + QTime worktime_to = worktime[w].getTimeUntil(); + + if (price_per_unit == 0) { + inputDate = inputDate.addDays(1); + inputDate.setTime(worktime_from); + double const partialCost = GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes, true, prepaid); + if (partialCost <= __DBL_MIN__) { + return 0.0; + } + costFromDuration += partialCost; + continue; + } + + // If overtime flag is set + if (overtime || nextDay) { + inputDate.setTime(worktime_from); + overtime = false; + } + + // Check prepaid + if (!prepaid) { + if ((inputDate.time() < worktime_from) || (inputDate.time() > worktime_to)) { + qDebug() << "[STOP] * Ticket is not valid * "; + return 0.0f; + } + } else { + qDebug() << "* PREPAID MODE ACTIVE *"; + if (inputDate.time() < worktime_from) { + inputDate.setTime(worktime_from); + } else if(inputDate.time() > worktime_to) { + qDebug() << " *** PREPAID *** Current time is past the time range end, searching for next available day"; + inputDate = inputDate.addDays(1); + double const partialCost = GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes, true, prepaid); + if (partialCost < __DBL_MIN__) { + return 0.0; + } + costFromDuration += partialCost; + continue; + } + } + + while(durationMinutes > 0) { + // Check for active year period + if (std::none_of(cfg->YearPeriod.begin(), + cfg->YearPeriod.end(), + [&inputDate](std::pair const &year) { + QDate const input(2004, // 2004 is a leap year + inputDate.date().month(), + inputDate.date().day()); + QDate const s(2004, year.second.pye_start_day, year.second.pye_start_month); + QDate const e(2004, year.second.pye_end_day, year.second.pye_end_month); + return (input >= s && input <= e); + })) { + qCritical() << "NO VALID YEAR PERIOD"; + return 0.0; + } + + // Go to next day if minutes not spent + if(inputDate.time() >= worktime_to) { + // check for carry_over status + if (cfg->PaymentOption.find(payment_option)->second.pop_carry_over < 1) { + break; + } + + qDebug() << "Reached end of worktime, searching for the next working day"; + + inputDate = inputDate.addDays(1); + overtime = true; + double const partialCost = GetCostFromDuration(cfg, payment_option, inputDate, end_datetime, durationMinutes, false, false); + if (partialCost < __DBL_MIN__) { + return 0.0; + } + costFromDuration += partialCost; + break; // stop while, and continue in outer loop + } else { + // Increment input date minutes for each monetary unit + inputDate = inputDate.addSecs(60); + durationMinutes -= 1; + costFromDuration += price_per_unit; + } + } + } + + qDebug() << "GetCostFromDuration(): Valid until:" << inputDate.toString(Qt::ISODate); + + end_datetime = inputDate; + + //double ret_val = total_cost; + //total_cost = 0.0f; + //return ceil(ret_val); + + // TODO: runden nur falls oberster stack-rahmen + + return ceil(costFromDuration); +} + + + +QList Calculator::GetTimeSteps(Configuration *cfg) const { + QList timeSteps; + + int const pop_id = cfg->getPaymentOptions().pop_id; + + for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) + { + int const durationId = itr->second.pra_payment_unit_id; + int const durationUnit = cfg->Duration.find(durationId)->second.pun_duration; + timeSteps << durationUnit; + } + + return timeSteps; +} + +uint32_t Calculator::GetPriceForTimeStep(Configuration *cfg, int timeStep) const { + + int const pop_id = cfg->getPaymentOptions().pop_id; + + for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) + { + int const payment_unit_id = itr->second.pra_payment_unit_id; + int const pun_id = cfg->Duration.find(payment_unit_id)->second.pun_id; + + Q_ASSERT(pun_id == payment_unit_id); + + int const pun_duration = cfg->Duration.find(payment_unit_id)->second.pun_duration; + if (timeStep == pun_duration) { + return (uint32_t)(itr->second.pra_price); + } + } + + return 0; +} + +uint32_t Calculator::GetDurationForPrice(Configuration *cfg, int price) const { + int const pop_id = cfg->getPaymentOptions().pop_id; + + uint32_t duration = 0; + + for (auto[itr, rangeEnd] = cfg->PaymentRate.equal_range(pop_id); itr != rangeEnd; ++itr) + { + int const durationId = itr->second.pra_payment_unit_id; + int const pra_price = itr->second.pra_price; + + uint32_t const durationUnit = cfg->Duration.find(durationId)->second.pun_duration; + + if (pra_price == price) { + return durationUnit; + } + + if (pra_price < price) { + duration = durationUnit; + } + } + + return duration; +}