File indexing completed on 2024-05-12 05:06:43

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