File indexing completed on 2024-05-12 16:42:40

0001 /*
0002     SPDX-FileCopyrightText: 2000-2004 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2002-2018 Thomas Baumgart <tbaumgart@kde.org>
0004     SPDX-FileCopyrightText: 2005 Ace Jones <acejones@users.sourceforge.net>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "mymoneyschedule.h"
0009 #include "mymoneyschedule_p.h"
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QList>
0015 #include <QMap>
0016 
0017 // ----------------------------------------------------------------------------
0018 // KDE Includes
0019 
0020 #include <KLocalizedString>
0021 
0022 // ----------------------------------------------------------------------------
0023 // Project Includes
0024 
0025 #include "mymoneyutils.h"
0026 #include "mymoneyexception.h"
0027 #include "mymoneyfile.h"
0028 #include "mymoneyaccount.h"
0029 #include "mymoneysplit.h"
0030 #include "imymoneyprocessingcalendar.h"
0031 
0032 using namespace eMyMoney;
0033 
0034 static IMyMoneyProcessingCalendar* processingCalendarPtr = 0;
0035 
0036 MyMoneySchedule::MyMoneySchedule() :
0037     MyMoneyObject(*new MyMoneySchedulePrivate)
0038 {
0039 }
0040 
0041 MyMoneySchedule::MyMoneySchedule(const QString &id) :
0042     MyMoneyObject(*new MyMoneySchedulePrivate, id)
0043 {
0044 }
0045 
0046 MyMoneySchedule::MyMoneySchedule(const QString& name,
0047                                  Schedule::Type type,
0048                                  Schedule::Occurrence occurrence,
0049                                  int occurrenceMultiplier,
0050                                  Schedule::PaymentType paymentType,
0051                                  const QDate& /* startDate */,
0052                                  const QDate& endDate,
0053                                  bool fixed,
0054                                  bool autoEnter) :
0055     MyMoneyObject(*new MyMoneySchedulePrivate)
0056 {
0057     Q_D(MyMoneySchedule);
0058     // Set up the values possibly differing from defaults
0059     d->m_name = name;
0060     d->m_occurrence = occurrence;
0061     d->m_occurrenceMultiplier = occurrenceMultiplier;
0062     simpleToCompoundOccurrence(d->m_occurrenceMultiplier, d->m_occurrence);
0063     d->m_type = type;
0064     d->m_paymentType = paymentType;
0065     d->m_fixed = fixed;
0066     d->m_autoEnter = autoEnter;
0067     d->m_endDate = endDate;
0068 }
0069 
0070 MyMoneySchedule::MyMoneySchedule(const MyMoneySchedule& other) :
0071     MyMoneyObject(*new MyMoneySchedulePrivate(*other.d_func()), other.id())
0072 {
0073 }
0074 
0075 MyMoneySchedule::MyMoneySchedule(const QString& id, const MyMoneySchedule& other) :
0076     MyMoneyObject(*new MyMoneySchedulePrivate(*other.d_func()), id)
0077 {
0078 }
0079 
0080 MyMoneySchedule::~MyMoneySchedule()
0081 {
0082 }
0083 
0084 Schedule::Occurrence MyMoneySchedule::baseOccurrence() const
0085 {
0086     Q_D(const MyMoneySchedule);
0087     Schedule::Occurrence occ = d->m_occurrence;
0088     int mult = d->m_occurrenceMultiplier;
0089     compoundToSimpleOccurrence(mult, occ);
0090     return occ;
0091 }
0092 
0093 int MyMoneySchedule::occurrenceMultiplier() const
0094 {
0095     Q_D(const MyMoneySchedule);
0096     return d->m_occurrenceMultiplier;
0097 }
0098 
0099 eMyMoney::Schedule::Type MyMoneySchedule::type() const
0100 {
0101     Q_D(const MyMoneySchedule);
0102     return d->m_type;
0103 }
0104 
0105 eMyMoney::Schedule::Occurrence MyMoneySchedule::occurrence() const
0106 {
0107     Q_D(const MyMoneySchedule);
0108     return d->m_occurrence;
0109 }
0110 
0111 void MyMoneySchedule::setStartDate(const QDate& date)
0112 {
0113     Q_D(MyMoneySchedule);
0114     d->m_startDate = date;
0115 }
0116 
0117 void MyMoneySchedule::setPaymentType(Schedule::PaymentType type)
0118 {
0119     Q_D(MyMoneySchedule);
0120     d->m_paymentType = type;
0121 }
0122 
0123 void MyMoneySchedule::setFixed(bool fixed)
0124 {
0125     Q_D(MyMoneySchedule);
0126     d->m_fixed = fixed;
0127 }
0128 
0129 void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction)
0130 {
0131     setTransaction(transaction, false);
0132 }
0133 
0134 void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck)
0135 {
0136     auto t = transaction;
0137     Q_D(MyMoneySchedule);
0138     if (!noDateCheck) {
0139         // don't allow a transaction that has no due date
0140         // if we get something like that, then we use the
0141         // the current next due date. If that is also invalid
0142         // we can't help it.
0143         if (!t.postDate().isValid()) {
0144             t.setPostDate(d->m_transaction.postDate());
0145         }
0146 
0147         if (!t.postDate().isValid())
0148             return;
0149     }
0150 
0151     // make sure to clear out some unused information in scheduled transactions
0152     // we need to do this for the case that the transaction passed as argument
0153     // is a matched or imported transaction.
0154     auto firstSplit = true;
0155     foreach (const auto split, t.splits()) {
0156         MyMoneySplit s = split;
0157         // clear out the bankID
0158         if (!split.bankID().isEmpty()) {
0159             s.setBankID(QString());
0160             t.modifySplit(s);
0161         }
0162 
0163         // only clear payees from second split onwards
0164         if (firstSplit) {
0165             firstSplit = false;
0166             continue;
0167         }
0168 
0169         if (!split.payeeId().isEmpty()) {
0170             // but only if the split references an income/expense category
0171             auto file = MyMoneyFile::instance();
0172             // some unit tests don't have a storage attached, so we
0173             // simply skip the test
0174             // Don't check for accounts with an id of 'Phony-ID' which is used
0175             // internally for non-existing accounts (during creation of accounts)
0176             if (file->storageAttached() && s.accountId() != QString("Phony-ID")) {
0177                 auto acc = file->account(s.accountId());
0178                 if (acc.isIncomeExpense()) {
0179                     s.setPayeeId(QString());
0180                     t.modifySplit(s);
0181                 }
0182             }
0183         }
0184     }
0185 
0186     d->m_transaction = t;
0187     // make sure that the transaction does not have an id so that we can enter
0188     // it into the engine
0189     d->m_transaction.clearId();
0190 }
0191 
0192 void MyMoneySchedule::setEndDate(const QDate& date)
0193 {
0194     Q_D(MyMoneySchedule);
0195     d->m_endDate = date;
0196 }
0197 
0198 void MyMoneySchedule::setLastDayInMonth(bool state)
0199 {
0200     Q_D(MyMoneySchedule);
0201     d->m_lastDayInMonth = state;
0202 }
0203 
0204 void MyMoneySchedule::setAutoEnter(bool autoenter)
0205 {
0206     Q_D(MyMoneySchedule);
0207     d->m_autoEnter = autoenter;
0208 }
0209 
0210 QDate MyMoneySchedule::startDate() const
0211 {
0212     Q_D(const MyMoneySchedule);
0213     if (d->m_startDate.isValid())
0214         return d->m_startDate;
0215     return nextDueDate();
0216 }
0217 
0218 eMyMoney::Schedule::PaymentType MyMoneySchedule::paymentType() const
0219 {
0220     Q_D(const MyMoneySchedule);
0221     return d->m_paymentType;
0222 }
0223 
0224 /**
0225   * Simple get method that returns true if the schedule is fixed.
0226   *
0227   * @return bool To indicate whether the instance is fixed.
0228   */
0229 bool MyMoneySchedule::isFixed() const
0230 {
0231     Q_D(const MyMoneySchedule);
0232     return d->m_fixed;
0233 }
0234 
0235 /**
0236   * Simple get method that returns true if the schedule will end
0237   * at some time.
0238   *
0239   * @return bool Indicates whether the instance will end.
0240   */
0241 bool MyMoneySchedule::willEnd() const
0242 {
0243     Q_D(const MyMoneySchedule);
0244     return d->m_endDate.isValid();
0245 }
0246 
0247 
0248 QDate MyMoneySchedule::nextDueDate() const
0249 {
0250     Q_D(const MyMoneySchedule);
0251 
0252     if (lastDayInMonth()) {
0253         const auto date = d->m_transaction.postDate();
0254         return adjustedDate(QDate(date.year(), date.month(), date.daysInMonth()), weekendOption());
0255     }
0256 
0257     return d->m_transaction.postDate();
0258 }
0259 
0260 QDate MyMoneySchedule::adjustedNextDueDate() const
0261 {
0262     if (isFinished())
0263         return QDate();
0264 
0265     return adjustedDate(nextDueDate(), weekendOption());
0266 }
0267 
0268 QDate MyMoneySchedule::adjustedDate(QDate date, Schedule::WeekendOption option) const
0269 {
0270     if (!date.isValid() || option == Schedule::WeekendOption::MoveNothing || isProcessingDate(date))
0271         return date;
0272 
0273     int step = 1;
0274     if (option == Schedule::WeekendOption::MoveBefore)
0275         step = -1;
0276 
0277     while (!isProcessingDate(date))
0278         date = date.addDays(step);
0279 
0280     return date;
0281 }
0282 
0283 void MyMoneySchedule::setNextDueDate(const QDate& date)
0284 {
0285     Q_D(MyMoneySchedule);
0286     if (date.isValid()) {
0287         d->m_transaction.setPostDate(date);
0288         // m_startDate = date;
0289     }
0290 }
0291 
0292 void MyMoneySchedule::setLastPayment(const QDate& date)
0293 {
0294     Q_D(MyMoneySchedule);
0295     // Delete all payments older than date
0296     QList<QDate>::Iterator it;
0297     QList<QDate> delList;
0298 
0299     for (it = d->m_recordedPayments.begin(); it != d->m_recordedPayments.end(); ++it) {
0300         if (*it < date || !date.isValid())
0301             delList.append(*it);
0302     }
0303 
0304     for (it = delList.begin(); it != delList.end(); ++it) {
0305         d->m_recordedPayments.removeAll(*it);
0306     }
0307 
0308     d->m_lastPayment = date;
0309     if (!d->m_startDate.isValid())
0310         d->m_startDate = date;
0311 }
0312 
0313 QString MyMoneySchedule::name() const
0314 {
0315     Q_D(const MyMoneySchedule);
0316     return d->m_name;
0317 }
0318 
0319 void MyMoneySchedule::setName(const QString& nm)
0320 {
0321     Q_D(MyMoneySchedule);
0322     d->m_name = nm;
0323 }
0324 
0325 eMyMoney::Schedule::WeekendOption MyMoneySchedule::weekendOption() const
0326 {
0327     Q_D(const MyMoneySchedule);
0328     return d->m_weekendOption;
0329 }
0330 
0331 void MyMoneySchedule::setOccurrence(Schedule::Occurrence occ)
0332 {
0333     auto occ2 = occ;
0334     auto mult = 1;
0335     simpleToCompoundOccurrence(mult, occ2);
0336     setOccurrencePeriod(occ2);
0337     setOccurrenceMultiplier(mult);
0338 }
0339 
0340 void MyMoneySchedule::setOccurrencePeriod(Schedule::Occurrence occ)
0341 {
0342     Q_D(MyMoneySchedule);
0343     d->m_occurrence = occ;
0344 }
0345 
0346 void MyMoneySchedule::setOccurrenceMultiplier(int occmultiplier)
0347 {
0348     Q_D(MyMoneySchedule);
0349     d->m_occurrenceMultiplier = occmultiplier < 1 ? 1 : occmultiplier;
0350 }
0351 
0352 void MyMoneySchedule::setType(Schedule::Type type)
0353 {
0354     Q_D(MyMoneySchedule);
0355     d->m_type = type;
0356 }
0357 
0358 void MyMoneySchedule::validate(bool id_check) const
0359 {
0360     /* Check the supplied instance is valid...
0361      *
0362      * To be valid it must not have the id set and have the following fields set:
0363      *
0364      * m_occurrence
0365      * m_type
0366      * m_startDate
0367      * m_paymentType
0368      * m_transaction
0369      *   the transaction must contain at least one split (two is better ;-)  )
0370      */
0371     Q_D(const MyMoneySchedule);
0372     if (id_check && !d->m_id.isEmpty())
0373         throw MYMONEYEXCEPTION_CSTRING("ID for schedule not empty when required");
0374 
0375     if (d->m_occurrence == Schedule::Occurrence::Any)
0376         throw MYMONEYEXCEPTION_CSTRING("Invalid occurrence type for schedule");
0377 
0378     if (d->m_type == Schedule::Type::Any)
0379         throw MYMONEYEXCEPTION_CSTRING("Invalid type for schedule");
0380 
0381     if (!nextDueDate().isValid())
0382         throw MYMONEYEXCEPTION_CSTRING("Invalid next due date for schedule");
0383 
0384     if (d->m_paymentType == Schedule::PaymentType::Any)
0385         throw MYMONEYEXCEPTION_CSTRING("Invalid payment type for schedule");
0386 
0387     if (d->m_transaction.splitCount() == 0)
0388         throw MYMONEYEXCEPTION_CSTRING("Scheduled transaction does not contain splits");
0389 
0390     // Check the payment types
0391     switch (d->m_type) {
0392     case Schedule::Type::Bill:
0393         if (d->m_paymentType == Schedule::PaymentType::DirectDeposit || d->m_paymentType == Schedule::PaymentType::ManualDeposit)
0394             throw MYMONEYEXCEPTION_CSTRING("Invalid payment type for bills");
0395         break;
0396 
0397     case Schedule::Type::Deposit:
0398         if (d->m_paymentType == Schedule::PaymentType::DirectDebit || d->m_paymentType == Schedule::PaymentType::WriteChecque)
0399             throw MYMONEYEXCEPTION_CSTRING("Invalid payment type for deposits");
0400         break;
0401 
0402     case Schedule::Type::Any:
0403         throw MYMONEYEXCEPTION_CSTRING("Invalid type ANY");
0404         break;
0405 
0406     case Schedule::Type::Transfer:
0407 //        if (m_paymentType == DirectDeposit || m_paymentType == ManualDeposit)
0408 //          return false;
0409         break;
0410 
0411     case Schedule::Type::LoanPayment:
0412         break;
0413     }
0414 }
0415 
0416 QDate MyMoneySchedule::adjustedNextPayment(const QDate& refDate) const
0417 {
0418     return nextPaymentDate(true, refDate);
0419 }
0420 
0421 QDate MyMoneySchedule::adjustedNextPayment() const
0422 {
0423     return adjustedNextPayment(QDate::currentDate());
0424 }
0425 
0426 QDate MyMoneySchedule::nextPayment(const QDate& refDate) const
0427 {
0428     return nextPaymentDate(false, refDate);
0429 }
0430 
0431 QDate MyMoneySchedule::nextPayment() const
0432 {
0433     return nextPayment(QDate::currentDate());
0434 }
0435 
0436 QDate MyMoneySchedule::nextPaymentDate(const bool& adjust, const QDate& refDate) const
0437 {
0438     Schedule::WeekendOption option(adjust ? weekendOption() :
0439                                    Schedule::WeekendOption::MoveNothing);
0440 
0441     Q_D(const MyMoneySchedule);
0442     QDate adjEndDate(adjustedDate(d->m_endDate, option));
0443 
0444     // if the enddate is valid and it is before the reference date,
0445     // then there will be no more payments.
0446     if (adjEndDate.isValid() && adjEndDate < refDate) {
0447         return QDate();
0448     }
0449 
0450     QDate dueDate(nextDueDate());
0451     QDate paymentDate(adjustedDate(dueDate, option));
0452 
0453     if (paymentDate.isValid() &&
0454             (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))) {
0455         switch (d->m_occurrence) {
0456         case Schedule::Occurrence::Once:
0457             // If the lastPayment is already set or the payment should have been
0458             // prior to the reference date then invalidate the payment date.
0459             if (d->m_lastPayment.isValid() || paymentDate <= refDate)
0460                 paymentDate = QDate();
0461             break;
0462 
0463         case Schedule::Occurrence::Daily: {
0464             int step = d->m_occurrenceMultiplier;
0465             do {
0466                 dueDate = dueDate.addDays(step);
0467                 paymentDate = adjustedDate(dueDate, option);
0468             } while (paymentDate.isValid() &&
0469                      (paymentDate <= refDate ||
0470                       d->m_recordedPayments.contains(dueDate)));
0471         }
0472         break;
0473 
0474         case Schedule::Occurrence::Weekly: {
0475             int step = 7 * d->m_occurrenceMultiplier;
0476             do {
0477                 dueDate = dueDate.addDays(step);
0478                 paymentDate = adjustedDate(dueDate, option);
0479             } while (paymentDate.isValid() &&
0480                      (paymentDate <= refDate ||
0481                       d->m_recordedPayments.contains(dueDate)));
0482         }
0483         break;
0484 
0485         case Schedule::Occurrence::EveryHalfMonth:
0486             do {
0487                 dueDate = addHalfMonths(dueDate, d->m_occurrenceMultiplier);
0488                 paymentDate = adjustedDate(dueDate, option);
0489             } while (paymentDate.isValid() &&
0490                      (paymentDate <= refDate ||
0491                       d->m_recordedPayments.contains(dueDate)));
0492             break;
0493 
0494         case Schedule::Occurrence::Monthly:
0495             do {
0496                 dueDate = dueDate.addMonths(d->m_occurrenceMultiplier);
0497                 fixDate(dueDate);
0498                 paymentDate = adjustedDate(dueDate, option);
0499             } while (paymentDate.isValid() &&
0500                      (paymentDate <= refDate ||
0501                       d->m_recordedPayments.contains(dueDate)));
0502             break;
0503 
0504         case Schedule::Occurrence::Yearly:
0505             do {
0506                 dueDate = dueDate.addYears(d->m_occurrenceMultiplier);
0507                 fixDate(dueDate);
0508                 paymentDate = adjustedDate(dueDate, option);
0509             } while (paymentDate.isValid() &&
0510                      (paymentDate <= refDate ||
0511                       d->m_recordedPayments.contains(dueDate)));
0512             break;
0513 
0514         case Schedule::Occurrence::Any:
0515         default:
0516             paymentDate = QDate();
0517             break;
0518         }
0519     }
0520     if (paymentDate.isValid() && adjEndDate.isValid() && paymentDate > adjEndDate)
0521         paymentDate = QDate();
0522 
0523     return paymentDate;
0524 }
0525 
0526 QDate MyMoneySchedule::nextPaymentDate(const bool& adjust) const
0527 {
0528     return nextPaymentDate(adjust, QDate::currentDate());
0529 }
0530 
0531 QList<QDate> MyMoneySchedule::paymentDates(const QDate& _startDate, const QDate& _endDate) const
0532 {
0533     QDate paymentDate(nextDueDate());
0534     QList<QDate> theDates;
0535 
0536     Schedule::WeekendOption option(weekendOption());
0537 
0538     Q_D(const MyMoneySchedule);
0539     QDate endDate(_endDate);
0540     if (willEnd() && d->m_endDate < endDate) {
0541         // consider the adjusted end date instead of the plain end date
0542         endDate = adjustedDate(d->m_endDate, option);
0543     }
0544 
0545     QDate start_date(adjustedDate(startDate(), option));
0546     // if the period specified by the parameters and the adjusted period
0547     // defined for this schedule don't overlap, then the list remains empty
0548     if ((willEnd() && adjustedDate(d->m_endDate, option) < _startDate)
0549             || start_date > endDate)
0550         return theDates;
0551 
0552     QDate date(adjustedDate(paymentDate, option));
0553 
0554     switch (d->m_occurrence) {
0555     case Schedule::Occurrence::Once:
0556         if (start_date >= _startDate && start_date <= endDate)
0557             theDates.append(start_date);
0558         break;
0559 
0560     case Schedule::Occurrence::Daily:
0561         while (date.isValid() && (date <= endDate)) {
0562             if (date >= _startDate)
0563                 theDates.append(date);
0564             paymentDate = paymentDate.addDays(d->m_occurrenceMultiplier);
0565             date = adjustedDate(paymentDate, option);
0566         }
0567         break;
0568 
0569     case Schedule::Occurrence::Weekly: {
0570         int step = 7 * d->m_occurrenceMultiplier;
0571         while (date.isValid() && (date <= endDate)) {
0572             if (date >= _startDate)
0573                 theDates.append(date);
0574             paymentDate = paymentDate.addDays(step);
0575             date = adjustedDate(paymentDate, option);
0576         }
0577     }
0578     break;
0579 
0580     case Schedule::Occurrence::EveryHalfMonth:
0581         while (date.isValid() && (date <= endDate)) {
0582             if (date >= _startDate)
0583                 theDates.append(date);
0584             paymentDate = addHalfMonths(paymentDate, d->m_occurrenceMultiplier);
0585             date = adjustedDate(paymentDate, option);
0586         }
0587         break;
0588 
0589     case Schedule::Occurrence::Monthly:
0590         while (date.isValid() && (date <= endDate)) {
0591             if (date >= _startDate)
0592                 theDates.append(date);
0593             paymentDate = paymentDate.addMonths(d->m_occurrenceMultiplier);
0594             fixDate(paymentDate);
0595             date = adjustedDate(paymentDate, option);
0596         }
0597         break;
0598 
0599     case Schedule::Occurrence::Yearly:
0600         while (date.isValid() && (date <= endDate)) {
0601             if (date >= _startDate)
0602                 theDates.append(date);
0603             paymentDate = paymentDate.addYears(d->m_occurrenceMultiplier);
0604             fixDate(paymentDate);
0605             date = adjustedDate(paymentDate, option);
0606         }
0607         break;
0608 
0609     case Schedule::Occurrence::Any:
0610     default:
0611         break;
0612     }
0613 
0614     return theDates;
0615 }
0616 
0617 bool MyMoneySchedule::operator <(const MyMoneySchedule& right) const
0618 {
0619     return adjustedNextDueDate() < right.adjustedNextDueDate();
0620 }
0621 
0622 bool MyMoneySchedule::operator ==(const MyMoneySchedule& right) const
0623 {
0624     Q_D(const MyMoneySchedule);
0625     auto d2 = static_cast<const MyMoneySchedulePrivate *>(right.d_func());
0626     // clang-format off
0627     if (MyMoneyObject::operator==(right)
0628         && d->m_occurrence == d2->m_occurrence
0629         && d->m_occurrenceMultiplier == d2->m_occurrenceMultiplier
0630         && d->m_type == d2->m_type
0631         && d->m_startDate == d2->m_startDate
0632         && d->m_paymentType == d2->m_paymentType
0633         && d->m_fixed == d2->m_fixed
0634         && d->m_transaction == d2->m_transaction
0635         && d->m_endDate == d2->m_endDate
0636         && d->m_lastDayInMonth == d2->m_lastDayInMonth
0637         && d->m_autoEnter == d2->m_autoEnter
0638         && d->m_lastPayment == d2->m_lastPayment
0639         && ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)))
0640         return true;
0641     // clang-format on
0642     return false;
0643 }
0644 
0645 bool MyMoneySchedule::operator !=(const MyMoneySchedule& right) const
0646 {
0647     return ! operator==(right);
0648 }
0649 
0650 int MyMoneySchedule::transactionsRemaining() const
0651 {
0652     Q_D(const MyMoneySchedule);
0653     return transactionsRemainingUntil(adjustedDate(d->m_endDate, weekendOption()));
0654 }
0655 
0656 int MyMoneySchedule::transactionsRemainingUntil(const QDate& endDate) const
0657 {
0658     auto counter = 0;
0659     Q_D(const MyMoneySchedule);
0660 
0661     const auto beginDate = d->m_lastPayment.isValid() ? d->m_lastPayment : startDate();
0662     if (beginDate.isValid() && endDate.isValid()) {
0663         QList<QDate> dates = paymentDates(beginDate, endDate);
0664         counter = dates.count();
0665     }
0666     return counter;
0667 }
0668 
0669 QDate MyMoneySchedule::endDate() const
0670 {
0671     Q_D(const MyMoneySchedule);
0672     return d->m_endDate;
0673 }
0674 
0675 bool MyMoneySchedule::autoEnter() const
0676 {
0677     Q_D(const MyMoneySchedule);
0678     return d->m_autoEnter;
0679 }
0680 
0681 bool MyMoneySchedule::lastDayInMonth() const
0682 {
0683     Q_D(const MyMoneySchedule);
0684     return d->m_lastDayInMonth;
0685 }
0686 
0687 MyMoneyTransaction MyMoneySchedule::transaction() const
0688 {
0689     Q_D(const MyMoneySchedule);
0690     return d->m_transaction;
0691 }
0692 
0693 QDate MyMoneySchedule::lastPayment() const
0694 {
0695     Q_D(const MyMoneySchedule);
0696     return d->m_lastPayment;
0697 }
0698 
0699 MyMoneyAccount MyMoneySchedule::account(int cnt) const
0700 {
0701     Q_D(const MyMoneySchedule);
0702     QList<MyMoneySplit> splits = d->m_transaction.splits();
0703     QList<MyMoneySplit>::ConstIterator it;
0704     auto file = MyMoneyFile::instance();
0705     MyMoneyAccount acc;
0706 
0707     // search the first asset or liability account
0708     for (it = splits.constBegin(); it != splits.constEnd() && (acc.id().isEmpty() || cnt); ++it) {
0709         try {
0710             acc = file->account((*it).accountId());
0711             if (acc.isAssetLiability())
0712                 --cnt;
0713 
0714             if (!cnt)
0715                 return acc;
0716         } catch (const MyMoneyException &) {
0717             qWarning("Schedule '%s' references unknown account '%s'", qPrintable(id()),   qPrintable((*it).accountId()));
0718             return MyMoneyAccount();
0719         }
0720     }
0721 
0722     return MyMoneyAccount();
0723 }
0724 
0725 MyMoneyAccount MyMoneySchedule::transferAccount() const {
0726     return account(2);
0727 }
0728 
0729 QDate MyMoneySchedule::dateAfter(int transactions) const
0730 {
0731     auto counter = 1;
0732     QDate paymentDate(startDate());
0733 
0734     if (transactions <= 0)
0735         return paymentDate;
0736 
0737     Q_D(const MyMoneySchedule);
0738     switch (d->m_occurrence) {
0739     case Schedule::Occurrence::Once:
0740         break;
0741 
0742     case Schedule::Occurrence::Daily:
0743         while (counter++ < transactions)
0744             paymentDate = paymentDate.addDays(d->m_occurrenceMultiplier);
0745         break;
0746 
0747     case Schedule::Occurrence::Weekly: {
0748         int step = 7 * d->m_occurrenceMultiplier;
0749         while (counter++ < transactions)
0750             paymentDate = paymentDate.addDays(step);
0751     }
0752     break;
0753 
0754     case Schedule::Occurrence::EveryHalfMonth:
0755         paymentDate = addHalfMonths(paymentDate, d->m_occurrenceMultiplier * (transactions - 1));
0756         break;
0757 
0758     case Schedule::Occurrence::Monthly:
0759         while (counter++ < transactions)
0760             paymentDate = paymentDate.addMonths(d->m_occurrenceMultiplier);
0761         break;
0762 
0763     case Schedule::Occurrence::Yearly:
0764         while (counter++ < transactions)
0765             paymentDate = paymentDate.addYears(d->m_occurrenceMultiplier);
0766         break;
0767 
0768     case Schedule::Occurrence::Any:
0769     default:
0770         break;
0771     }
0772 
0773     return paymentDate;
0774 }
0775 
0776 bool MyMoneySchedule::isOverdue() const
0777 {
0778     if (isFinished())
0779         return false;
0780 
0781     if (adjustedNextDueDate() >= QDate::currentDate())
0782         return false;
0783 
0784     return true;
0785 }
0786 
0787 bool MyMoneySchedule::isFinished() const
0788 {
0789     Q_D(const MyMoneySchedule);
0790     if (!d->m_lastPayment.isValid())
0791         return false;
0792 
0793     if (d->m_endDate.isValid()) {
0794         if (d->m_lastPayment >= d->m_endDate
0795                 || !nextDueDate().isValid()
0796                 || nextDueDate() > d->m_endDate)
0797             return true;
0798     }
0799 
0800     // Check to see if its a once off payment
0801     if (d->m_occurrence == Schedule::Occurrence::Once)
0802         return true;
0803 
0804     return false;
0805 }
0806 
0807 bool MyMoneySchedule::hasRecordedPayment(const QDate& date) const
0808 {
0809     Q_D(const MyMoneySchedule);
0810     // m_lastPayment should always be > recordedPayments()
0811     if (d->m_lastPayment.isValid() && d->m_lastPayment >= date)
0812         return true;
0813 
0814     if (d->m_recordedPayments.contains(date))
0815         return true;
0816 
0817     return false;
0818 }
0819 
0820 void MyMoneySchedule::recordPayment(const QDate& date)
0821 {
0822     Q_D(MyMoneySchedule);
0823     d->m_recordedPayments.append(date);
0824 }
0825 
0826 QList<QDate> MyMoneySchedule::recordedPayments() const
0827 {
0828     Q_D(const MyMoneySchedule);
0829     return d->m_recordedPayments;
0830 }
0831 
0832 void MyMoneySchedule::setWeekendOption(const Schedule::WeekendOption option)
0833 {
0834     Q_D(MyMoneySchedule);
0835     // make sure only valid values are used. Invalid defaults to MoveNothing.
0836     switch (option) {
0837     case Schedule::WeekendOption::MoveBefore:
0838     case Schedule::WeekendOption::MoveAfter:
0839         d->m_weekendOption = option;
0840         break;
0841 
0842     default:
0843         d->m_weekendOption = Schedule::WeekendOption::MoveNothing;
0844         break;
0845     }
0846 }
0847 
0848 void MyMoneySchedule::fixDate(QDate& date) const
0849 {
0850     Q_D(const MyMoneySchedule);
0851     QDate fixDate(d->m_startDate);
0852 
0853     if (d->m_lastDayInMonth) {
0854         fixDate = QDate(fixDate.year(), fixDate.month(), fixDate.daysInMonth());
0855     }
0856 
0857     if (fixDate.isValid()
0858             && date.day() != fixDate.day()
0859             && QDate::isValid(date.year(), date.month(), fixDate.day())) {
0860         date = QDate(date.year(), date.month(), fixDate.day());
0861     }
0862 }
0863 
0864 bool MyMoneySchedule::hasReferenceTo(const QString& id) const
0865 {
0866     Q_D(const MyMoneySchedule);
0867     return d->m_transaction.hasReferenceTo(id);
0868 }
0869 
0870 QString MyMoneySchedule::occurrenceToString() const
0871 {
0872     return occurrenceToString(occurrenceMultiplier(), occurrence());
0873 }
0874 
0875 QString MyMoneySchedule::occurrenceToString(Schedule::Occurrence occurrence)
0876 {
0877     QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any");
0878 
0879     if (occurrence == Schedule::Occurrence::Once)
0880         occurrenceString = I18N_NOOP2("Frequency of schedule", "Once");
0881     else if (occurrence == Schedule::Occurrence::Daily)
0882         occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily");
0883     else if (occurrence == Schedule::Occurrence::Weekly)
0884         occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly");
0885     else if (occurrence == Schedule::Occurrence::Fortnightly)
0886         occurrenceString = I18N_NOOP2("Frequency of schedule", "Fortnightly");
0887     else if (occurrence == Schedule::Occurrence::EveryOtherWeek)
0888         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week");
0889     else if (occurrence == Schedule::Occurrence::EveryHalfMonth)
0890         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month");
0891     else if (occurrence == Schedule::Occurrence::EveryThreeWeeks)
0892         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks");
0893     else if (occurrence == Schedule::Occurrence::EveryFourWeeks)
0894         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks");
0895     else if (occurrence == Schedule::Occurrence::EveryThirtyDays)
0896         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days");
0897     else if (occurrence == Schedule::Occurrence::Monthly)
0898         occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly");
0899     else if (occurrence == Schedule::Occurrence::EveryEightWeeks)
0900         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks");
0901     else if (occurrence == Schedule::Occurrence::EveryOtherMonth)
0902         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months");
0903     else if (occurrence == Schedule::Occurrence::EveryThreeMonths)
0904         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months");
0905     else if (occurrence == Schedule::Occurrence::Quarterly)
0906         occurrenceString = I18N_NOOP2("Frequency of schedule", "Quarterly");
0907     else if (occurrence == Schedule::Occurrence::EveryFourMonths)
0908         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months");
0909     else if (occurrence == Schedule::Occurrence::TwiceYearly)
0910         occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly");
0911     else if (occurrence == Schedule::Occurrence::Yearly)
0912         occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly");
0913     else if (occurrence == Schedule::Occurrence::EveryOtherYear)
0914         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year");
0915     return occurrenceString;
0916 }
0917 
0918 QString MyMoneySchedule::occurrenceToString(int mult, Schedule::Occurrence type)
0919 {
0920     QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any");
0921 
0922     if (type == Schedule::Occurrence::Once)
0923         switch (mult) {
0924         case 1:
0925             occurrenceString = I18N_NOOP2("Frequency of schedule", "Once");
0926             break;
0927         default:
0928             occurrenceString = I18N_NOOP2("Frequency of schedule", QString("%1 times").arg(mult));
0929         }
0930     else if (type == Schedule::Occurrence::Daily)
0931         switch (mult) {
0932         case 1:
0933             occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily");
0934             break;
0935         case 30:
0936             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days");
0937             break;
0938         default:
0939             occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 days").arg(mult));
0940         }
0941     else if (type == Schedule::Occurrence::Weekly)
0942         switch (mult) {
0943         case 1:
0944             occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly");
0945             break;
0946         case 2:
0947             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week");
0948             break;
0949         case 3:
0950             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks");
0951             break;
0952         case 4:
0953             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks");
0954             break;
0955         case 8:
0956             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks");
0957             break;
0958         default:
0959             occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 weeks").arg(mult));
0960         }
0961     else if (type == Schedule::Occurrence::EveryHalfMonth)
0962         switch (mult) {
0963         case 1:
0964             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month");
0965             break;
0966         default:
0967             occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 half months").arg(mult));
0968         }
0969     else if (type == Schedule::Occurrence::Monthly)
0970         switch (mult) {
0971         case 1:
0972             occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly");
0973             break;
0974         case 2:
0975             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months");
0976             break;
0977         case 3:
0978             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months");
0979             break;
0980         case 4:
0981             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months");
0982             break;
0983         case 6:
0984             occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly");
0985             break;
0986         default:
0987             occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 months").arg(mult));
0988         }
0989     else if (type == Schedule::Occurrence::Yearly)
0990         switch (mult) {
0991         case 1:
0992             occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly");
0993             break;
0994         case 2:
0995             occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year");
0996             break;
0997         default:
0998             occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 years").arg(mult));
0999         }
1000     return occurrenceString;
1001 }
1002 
1003 QString MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence type)
1004 {
1005     QString occurrenceString = I18N_NOOP2("Schedule occurrence period", "Any");
1006 
1007     if (type == Schedule::Occurrence::Once)
1008         occurrenceString = I18N_NOOP2("Schedule occurrence period", "Once");
1009     else if (type == Schedule::Occurrence::Daily)
1010         occurrenceString = I18N_NOOP2("Schedule occurrence period", "Day");
1011     else if (type == Schedule::Occurrence::Weekly)
1012         occurrenceString = I18N_NOOP2("Schedule occurrence period", "Week");
1013     else if (type == Schedule::Occurrence::EveryHalfMonth)
1014         occurrenceString = I18N_NOOP2("Schedule occurrence period", "Half-month");
1015     else if (type == Schedule::Occurrence::Monthly)
1016         occurrenceString = I18N_NOOP2("Schedule occurrence period", "Month");
1017     else if (type == Schedule::Occurrence::Yearly)
1018         occurrenceString = I18N_NOOP2("Schedule occurrence period", "Year");
1019     return occurrenceString;
1020 }
1021 
1022 QString MyMoneySchedule::scheduleTypeToString(Schedule::Type type)
1023 {
1024     QString text;
1025 
1026     switch (type) {
1027     case Schedule::Type::Bill:
1028         text = I18N_NOOP2("Scheduled transaction type", "Bill");
1029         break;
1030     case Schedule::Type::Deposit:
1031         text = I18N_NOOP2("Scheduled transaction type", "Deposit");
1032         break;
1033     case Schedule::Type::Transfer:
1034         text = I18N_NOOP2("Scheduled transaction type", "Transfer");
1035         break;
1036     case Schedule::Type::LoanPayment:
1037         text = I18N_NOOP2("Scheduled transaction type", "Loan payment");
1038         break;
1039     case Schedule::Type::Any:
1040     default:
1041         text = I18N_NOOP2("Scheduled transaction type", "Unknown");
1042     }
1043     return text;
1044 }
1045 
1046 
1047 QString MyMoneySchedule::paymentMethodToString(Schedule::PaymentType paymentType)
1048 {
1049     QString text;
1050 
1051     switch (paymentType) {
1052     case Schedule::PaymentType::DirectDebit:
1053         text = I18N_NOOP2("Scheduled Transaction payment type", "Direct debit");
1054         break;
1055     case Schedule::PaymentType::DirectDeposit:
1056         text = I18N_NOOP2("Scheduled Transaction payment type", "Direct deposit");
1057         break;
1058     case Schedule::PaymentType::ManualDeposit:
1059         text = I18N_NOOP2("Scheduled Transaction payment type", "Manual deposit");
1060         break;
1061     case Schedule::PaymentType::Other:
1062         text = I18N_NOOP2("Scheduled Transaction payment type", "Other");
1063         break;
1064     case Schedule::PaymentType::WriteChecque:
1065         text = I18N_NOOP2("Scheduled Transaction payment type", "Write check");
1066         break;
1067     case Schedule::PaymentType::StandingOrder:
1068         text = I18N_NOOP2("Scheduled Transaction payment type", "Standing order");
1069         break;
1070     case Schedule::PaymentType::BankTransfer:
1071         text = I18N_NOOP2("Scheduled Transaction payment type", "Bank transfer");
1072         break;
1073     case Schedule::PaymentType::Any:
1074         text = I18N_NOOP2("Scheduled Transaction payment type", "Any (Error)");
1075         break;
1076     }
1077     return text;
1078 }
1079 
1080 QString MyMoneySchedule::weekendOptionToString(Schedule::WeekendOption weekendOption)
1081 {
1082     QString text;
1083 
1084     switch (weekendOption) {
1085     case Schedule::WeekendOption::MoveBefore:
1086         text = I18N_NOOP("Change the date to the previous processing day");
1087         break;
1088     case Schedule::WeekendOption::MoveAfter:
1089         text = I18N_NOOP("Change the date to the next processing day");
1090         break;
1091     case Schedule::WeekendOption::MoveNothing:
1092         text = I18N_NOOP("Do not change the date");
1093         break;
1094     }
1095     return text;
1096 }
1097 
1098 // until we don't have the means to store the value
1099 // of the variation, we default to 10% in case this
1100 // scheduled transaction is marked 'not fixed'.
1101 //
1102 // ipwizard 2009-04-18
1103 
1104 int MyMoneySchedule::variation() const
1105 {
1106     int rc = 0;
1107     if (!isFixed()) {
1108         rc = 10;
1109 #if 0
1110         QString var = value("kmm-variation");
1111         if (!var.isEmpty())
1112             rc = var.toInt();
1113 #endif
1114     }
1115     return rc;
1116 }
1117 
1118 void MyMoneySchedule::setVariation(int var)
1119 {
1120     Q_UNUSED(var)
1121 #if 0
1122     deletePair("kmm-variation");
1123     if (var != 0)
1124         setValue("kmm-variation", QString("%1").arg(var));
1125 #endif
1126 }
1127 
1128 int MyMoneySchedule::eventsPerYear(Schedule::Occurrence occurrence)
1129 {
1130     int rc = 0;
1131 
1132     switch (occurrence) {
1133     case Schedule::Occurrence::Daily:
1134         rc = 365;
1135         break;
1136     case Schedule::Occurrence::Weekly:
1137         rc = 52;
1138         break;
1139     case Schedule::Occurrence::Fortnightly:
1140         rc = 26;
1141         break;
1142     case Schedule::Occurrence::EveryOtherWeek:
1143         rc = 26;
1144         break;
1145     case Schedule::Occurrence::EveryHalfMonth:
1146         rc = 24;
1147         break;
1148     case Schedule::Occurrence::EveryThreeWeeks:
1149         rc = 17;
1150         break;
1151     case Schedule::Occurrence::EveryFourWeeks:
1152         rc = 13;
1153         break;
1154     case Schedule::Occurrence::Monthly:
1155     case Schedule::Occurrence::EveryThirtyDays:
1156         rc = 12;
1157         break;
1158     case Schedule::Occurrence::EveryEightWeeks:
1159         rc = 6;
1160         break;
1161     case Schedule::Occurrence::EveryOtherMonth:
1162         rc = 6;
1163         break;
1164     case Schedule::Occurrence::EveryThreeMonths:
1165     case Schedule::Occurrence::Quarterly:
1166         rc = 4;
1167         break;
1168     case Schedule::Occurrence::EveryFourMonths:
1169         rc = 3;
1170         break;
1171     case Schedule::Occurrence::TwiceYearly:
1172         rc = 2;
1173         break;
1174     case Schedule::Occurrence::Yearly:
1175         rc = 1;
1176         break;
1177     default:
1178         qWarning("Occurrence not supported by financial calculator");
1179     }
1180 
1181     return rc;
1182 }
1183 
1184 int MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence occurrence)
1185 {
1186     int rc = 0;
1187 
1188     switch (occurrence) {
1189     case Schedule::Occurrence::Daily:
1190         rc = 1;
1191         break;
1192     case Schedule::Occurrence::Weekly:
1193         rc = 7;
1194         break;
1195     case Schedule::Occurrence::Fortnightly:
1196         rc = 14;
1197         break;
1198     case Schedule::Occurrence::EveryOtherWeek:
1199         rc = 14;
1200         break;
1201     case Schedule::Occurrence::EveryHalfMonth:
1202         rc = 15;
1203         break;
1204     case Schedule::Occurrence::EveryThreeWeeks:
1205         rc = 21;
1206         break;
1207     case Schedule::Occurrence::EveryFourWeeks:
1208         rc = 28;
1209         break;
1210     case Schedule::Occurrence::EveryThirtyDays:
1211         rc = 30;
1212         break;
1213     case Schedule::Occurrence::Monthly:
1214         rc = 30;
1215         break;
1216     case Schedule::Occurrence::EveryEightWeeks:
1217         rc = 56;
1218         break;
1219     case Schedule::Occurrence::EveryOtherMonth:
1220         rc = 60;
1221         break;
1222     case Schedule::Occurrence::EveryThreeMonths:
1223     case Schedule::Occurrence::Quarterly:
1224         rc = 90;
1225         break;
1226     case Schedule::Occurrence::EveryFourMonths:
1227         rc = 120;
1228         break;
1229     case Schedule::Occurrence::TwiceYearly:
1230         rc = 180;
1231         break;
1232     case Schedule::Occurrence::Yearly:
1233         rc = 360;
1234         break;
1235     default:
1236         qWarning("Occurrence not supported by financial calculator");
1237     }
1238 
1239     return rc;
1240 }
1241 
1242 QDate MyMoneySchedule::addHalfMonths(QDate date, int mult) const
1243 {
1244     QDate newdate = date;
1245     int d, dm;
1246     if (mult > 0) {
1247         d = newdate.day();
1248         if (d <= 12) {
1249             if (mult % 2 == 0)
1250                 newdate = newdate.addMonths(mult >> 1);
1251             else
1252                 newdate = newdate.addMonths(mult >> 1).addDays(15);
1253         } else
1254             for (int i = 0; i < mult; i++) {
1255                 if (d <= 13)
1256                     newdate = newdate.addDays(15);
1257                 else {
1258                     dm = newdate.daysInMonth();
1259                     if (d == 14)
1260                         newdate = newdate.addDays((dm < 30) ? dm - d : 15);
1261                     else if (d == 15)
1262                         newdate = newdate.addDays(dm - d);
1263                     else if (d == dm)
1264                         newdate = newdate.addDays(15 - d).addMonths(1);
1265                     else
1266                         newdate = newdate.addDays(-15).addMonths(1);
1267                 }
1268                 d = newdate.day();
1269             }
1270     } else if (mult < 0)  // Go backwards
1271         for (int i = 0; i > mult; i--) {
1272             d = newdate.day();
1273             dm = newdate.daysInMonth();
1274             if (d > 15) {
1275                 dm = newdate.daysInMonth();
1276                 newdate = newdate.addDays((d == dm) ? 15 - dm : -15);
1277             } else if (d <= 13)
1278                 newdate = newdate.addMonths(-1).addDays(15);
1279             else if (d == 15)
1280                 newdate = newdate.addDays(-15);
1281             else { // 14
1282                 newdate = newdate.addMonths(-1);
1283                 dm = newdate.daysInMonth();
1284                 newdate = newdate.addDays((dm < 30) ? dm - d : 15);
1285             }
1286         }
1287     return newdate;
1288 }
1289 
1290 /**
1291   * Helper method to convert simple occurrence to compound occurrence + multiplier
1292   *
1293   * @param multiplier Returned by reference.  Adjusted multiplier
1294   * @param occurrence Returned by reference.  Occurrence type
1295   */
1296 void MyMoneySchedule::simpleToCompoundOccurrence(int& multiplier, Schedule::Occurrence& occurrence)
1297 {
1298     Schedule::Occurrence newOcc = occurrence;
1299     int newMulti = 1;
1300     if (occurrence == Schedule::Occurrence::Once //
1301         || occurrence == Schedule::Occurrence::Daily //
1302         || occurrence == Schedule::Occurrence::Weekly //
1303         || occurrence == Schedule::Occurrence::EveryHalfMonth //
1304         || occurrence == Schedule::Occurrence::Monthly //
1305         || occurrence == Schedule::Occurrence::Yearly) { // Already a base occurrence and multiplier
1306     } else if (occurrence == Schedule::Occurrence::Fortnightly ||
1307                occurrence == Schedule::Occurrence::EveryOtherWeek) {
1308         newOcc    = Schedule::Occurrence::Weekly;
1309         newMulti  = 2;
1310     } else if (occurrence == Schedule::Occurrence::EveryThreeWeeks) {
1311         newOcc    = Schedule::Occurrence::Weekly;
1312         newMulti  = 3;
1313     } else if (occurrence == Schedule::Occurrence::EveryFourWeeks) {
1314         newOcc    = Schedule::Occurrence::Weekly;
1315         newMulti  = 4;
1316     } else if (occurrence == Schedule::Occurrence::EveryThirtyDays) {
1317         newOcc    = Schedule::Occurrence::Daily;
1318         newMulti  = 30;
1319     } else if (occurrence == Schedule::Occurrence::EveryEightWeeks) {
1320         newOcc    = Schedule::Occurrence::Weekly;
1321         newMulti  = 8;
1322     } else if (occurrence == Schedule::Occurrence::EveryOtherMonth) {
1323         newOcc    = Schedule::Occurrence::Monthly;
1324         newMulti  = 2;
1325     } else if (occurrence == Schedule::Occurrence::EveryThreeMonths //
1326             || occurrence == Schedule::Occurrence::Quarterly) {
1327         newOcc    = Schedule::Occurrence::Monthly;
1328         newMulti  = 3;
1329     } else if (occurrence == Schedule::Occurrence::EveryFourMonths) {
1330         newOcc    = Schedule::Occurrence::Monthly;
1331         newMulti  = 4;
1332     } else if (occurrence == Schedule::Occurrence::TwiceYearly) {
1333         newOcc    = Schedule::Occurrence::Monthly;
1334         newMulti  = 6;
1335     } else if (occurrence == Schedule::Occurrence::EveryOtherYear) {
1336         newOcc    = Schedule::Occurrence::Yearly;
1337         newMulti  = 2;
1338     } else { // Unknown
1339         newOcc    = Schedule::Occurrence::Any;
1340         newMulti  = 1;
1341     }
1342     if (newOcc != occurrence) {
1343         occurrence   = newOcc;
1344         multiplier  = newMulti == 1 ? multiplier : newMulti * multiplier;
1345     }
1346 }
1347 
1348 /**
1349   * Helper method to convert compound occurrence + multiplier to simple occurrence
1350   *
1351   * @param multiplier Returned by reference.  Adjusted multiplier
1352   * @param occurrence Returned by reference.  Occurrence type
1353   */
1354 void MyMoneySchedule::compoundToSimpleOccurrence(int& multiplier, Schedule::Occurrence& occurrence)
1355 {
1356     Schedule::Occurrence newOcc = occurrence;
1357     if (occurrence == Schedule::Occurrence::Once) { // Nothing to do
1358     } else if (occurrence == Schedule::Occurrence::Daily) {
1359         switch (multiplier) {
1360         case 1:
1361             break;
1362         case 30:
1363             newOcc = Schedule::Occurrence::EveryThirtyDays;
1364             break;
1365         }
1366     } else if (newOcc == Schedule::Occurrence::Weekly) {
1367         switch (multiplier) {
1368         case 1:
1369             break;
1370         case 2:
1371             newOcc = Schedule::Occurrence::EveryOtherWeek;
1372             break;
1373         case 3:
1374             newOcc = Schedule::Occurrence::EveryThreeWeeks;
1375             break;
1376         case 4:
1377             newOcc = Schedule::Occurrence::EveryFourWeeks;
1378             break;
1379         case 8:
1380             newOcc = Schedule::Occurrence::EveryEightWeeks;
1381             break;
1382         }
1383     } else if (occurrence == Schedule::Occurrence::Monthly)
1384         switch (multiplier) {
1385         case 1:
1386             break;
1387         case 2:
1388             newOcc = Schedule::Occurrence::EveryOtherMonth;
1389             break;
1390         case 3:
1391             newOcc = Schedule::Occurrence::EveryThreeMonths;
1392             break;
1393         case 4:
1394             newOcc = Schedule::Occurrence::EveryFourMonths;
1395             break;
1396         case 6:
1397             newOcc = Schedule::Occurrence::TwiceYearly;
1398             break;
1399         }
1400     else if (occurrence == Schedule::Occurrence::EveryHalfMonth)
1401         switch (multiplier) {
1402         case 1:
1403             break;
1404         }
1405     else if (occurrence == Schedule::Occurrence::Yearly) {
1406         switch (multiplier) {
1407         case 1:
1408             break;
1409         case 2:
1410             newOcc = Schedule::Occurrence::EveryOtherYear;
1411             break;
1412         }
1413     }
1414     if (occurrence != newOcc) { // Changed to derived type
1415         occurrence = newOcc;
1416         multiplier = 1;
1417     }
1418 }
1419 
1420 void MyMoneySchedule::setProcessingCalendar(IMyMoneyProcessingCalendar* pc)
1421 {
1422     processingCalendarPtr = pc;
1423 }
1424 
1425 bool MyMoneySchedule::isProcessingDate(const QDate& date) const
1426 {
1427     if (processingCalendarPtr)
1428         return processingCalendarPtr->isProcessingDate(date);
1429 
1430     /// @todo test against m_processingDays instead?  (currently only for tests)
1431     return date.dayOfWeek() < Qt::Saturday;
1432 }
1433 
1434 IMyMoneyProcessingCalendar* MyMoneySchedule::processingCalendar() const
1435 {
1436     return processingCalendarPtr;
1437 }
1438 
1439 bool MyMoneySchedule::replaceId(const QString& newId, const QString& oldId)
1440 {
1441     Q_D(MyMoneySchedule);
1442     return d->m_transaction.replaceId(newId, oldId);
1443 }