1 /*
2  * Copyright 2000-2004  Michael Edwardes <mte@users.sourceforge.net>
3  * Copyright 2002-2018  Thomas Baumgart <tbaumgart@kde.org>
4  * Copyright 2005       Ace Jones <acejones@users.sourceforge.net>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "mymoneyschedule.h"
21 #include "mymoneyschedule_p.h"
22 
23 // ----------------------------------------------------------------------------
24 // QT Includes
25 
26 #include <QList>
27 #include <QMap>
28 
29 // ----------------------------------------------------------------------------
30 // KDE Includes
31 
32 #include <KLocalizedString>
33 
34 // ----------------------------------------------------------------------------
35 // Project Includes
36 
37 #include "mymoneyutils.h"
38 #include "mymoneyexception.h"
39 #include "mymoneyfile.h"
40 #include "mymoneyaccount.h"
41 #include "mymoneysplit.h"
42 #include "imymoneyprocessingcalendar.h"
43 
44 using namespace eMyMoney;
45 
46 static IMyMoneyProcessingCalendar* processingCalendarPtr = 0;
47 
MyMoneySchedule()48 MyMoneySchedule::MyMoneySchedule() :
49     MyMoneyObject(*new MyMoneySchedulePrivate)
50 {
51 }
52 
MyMoneySchedule(const QString & id)53 MyMoneySchedule::MyMoneySchedule(const QString &id) :
54     MyMoneyObject(*new MyMoneySchedulePrivate, id)
55 {
56 }
57 
MyMoneySchedule(const QString & name,Schedule::Type type,Schedule::Occurrence occurrence,int occurrenceMultiplier,Schedule::PaymentType paymentType,const QDate &,const QDate & endDate,bool fixed,bool autoEnter)58 MyMoneySchedule::MyMoneySchedule(const QString& name,
59                                  Schedule::Type type,
60                                  Schedule::Occurrence occurrence,
61                                  int occurrenceMultiplier,
62                                  Schedule::PaymentType paymentType,
63                                  const QDate& /* startDate */,
64                                  const QDate& endDate,
65                                  bool fixed,
66                                  bool autoEnter) :
67     MyMoneyObject(*new MyMoneySchedulePrivate)
68 {
69   Q_D(MyMoneySchedule);
70   // Set up the values possibly differing from defaults
71   d->m_name = name;
72   d->m_occurrence = occurrence;
73   d->m_occurrenceMultiplier = occurrenceMultiplier;
74   simpleToCompoundOccurrence(d->m_occurrenceMultiplier, d->m_occurrence);
75   d->m_type = type;
76   d->m_paymentType = paymentType;
77   d->m_fixed = fixed;
78   d->m_autoEnter = autoEnter;
79   d->m_endDate = endDate;
80 }
81 
MyMoneySchedule(const MyMoneySchedule & other)82 MyMoneySchedule::MyMoneySchedule(const MyMoneySchedule& other) :
83   MyMoneyObject(*new MyMoneySchedulePrivate(*other.d_func()), other.id())
84 {
85 }
86 
MyMoneySchedule(const QString & id,const MyMoneySchedule & other)87 MyMoneySchedule::MyMoneySchedule(const QString& id, const MyMoneySchedule& other) :
88   MyMoneyObject(*new MyMoneySchedulePrivate(*other.d_func()), id)
89 {
90 }
91 
~MyMoneySchedule()92 MyMoneySchedule::~MyMoneySchedule()
93 {
94 }
95 
baseOccurrence() const96 Schedule::Occurrence MyMoneySchedule::baseOccurrence() const
97 {
98   Q_D(const MyMoneySchedule);
99   Schedule::Occurrence occ = d->m_occurrence;
100   int mult = d->m_occurrenceMultiplier;
101   compoundToSimpleOccurrence(mult, occ);
102   return occ;
103 }
104 
occurrenceMultiplier() const105 int MyMoneySchedule::occurrenceMultiplier() const
106 {
107   Q_D(const MyMoneySchedule);
108   return d->m_occurrenceMultiplier;
109 }
110 
type() const111 eMyMoney::Schedule::Type MyMoneySchedule::type() const
112 {
113   Q_D(const MyMoneySchedule);
114   return d->m_type;
115 }
116 
occurrence() const117 eMyMoney::Schedule::Occurrence MyMoneySchedule::occurrence() const
118 {
119   Q_D(const MyMoneySchedule);
120   return d->m_occurrence;
121 }
122 
setStartDate(const QDate & date)123 void MyMoneySchedule::setStartDate(const QDate& date)
124 {
125   Q_D(MyMoneySchedule);
126   d->m_startDate = date;
127 }
128 
setPaymentType(Schedule::PaymentType type)129 void MyMoneySchedule::setPaymentType(Schedule::PaymentType type)
130 {
131   Q_D(MyMoneySchedule);
132   d->m_paymentType = type;
133 }
134 
setFixed(bool fixed)135 void MyMoneySchedule::setFixed(bool fixed)
136 {
137   Q_D(MyMoneySchedule);
138   d->m_fixed = fixed;
139 }
140 
setTransaction(const MyMoneyTransaction & transaction)141 void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction)
142 {
143   setTransaction(transaction, false);
144 }
145 
setTransaction(const MyMoneyTransaction & transaction,bool noDateCheck)146 void MyMoneySchedule::setTransaction(const MyMoneyTransaction& transaction, bool noDateCheck)
147 {
148   auto t = transaction;
149   Q_D(MyMoneySchedule);
150   if (!noDateCheck) {
151     // don't allow a transaction that has no due date
152     // if we get something like that, then we use the
153     // the current next due date. If that is also invalid
154     // we can't help it.
155     if (!t.postDate().isValid()) {
156       t.setPostDate(d->m_transaction.postDate());
157     }
158 
159     if (!t.postDate().isValid())
160       return;
161   }
162 
163   // make sure to clear out some unused information in scheduled transactions
164   // we need to do this for the case that the transaction passed as argument
165   // is a matched or imported transaction.
166   auto firstSplit = true;
167   foreach (const auto split, t.splits()) {
168     MyMoneySplit s = split;
169     // clear out the bankID
170     if (!split.bankID().isEmpty()) {
171       s.setBankID(QString());
172       t.modifySplit(s);
173     }
174 
175     // only clear payees from second split onwards
176     if (firstSplit) {
177       firstSplit = false;
178       continue;
179     }
180 
181     if (!split.payeeId().isEmpty()) {
182       // but only if the split references an income/expense category
183       auto file = MyMoneyFile::instance();
184       // some unit tests don't have a storage attached, so we
185       // simply skip the test
186       // Don't check for accounts with an id of 'Phony-ID' which is used
187       // internally for non-existing accounts (during creation of accounts)
188       if (file->storageAttached() && s.accountId() != QString("Phony-ID")) {
189         auto acc = file->account(s.accountId());
190         if (acc.isIncomeExpense()) {
191           s.setPayeeId(QString());
192           t.modifySplit(s);
193         }
194       }
195     }
196   }
197 
198   d->m_transaction = t;
199   // make sure that the transaction does not have an id so that we can enter
200   // it into the engine
201   d->m_transaction.clearId();
202 }
203 
setEndDate(const QDate & date)204 void MyMoneySchedule::setEndDate(const QDate& date)
205 {
206   Q_D(MyMoneySchedule);
207   d->m_endDate = date;
208 }
209 
setLastDayInMonth(bool state)210 void MyMoneySchedule::setLastDayInMonth(bool state)
211 {
212   Q_D(MyMoneySchedule);
213   d->m_lastDayInMonth = state;
214 }
215 
setAutoEnter(bool autoenter)216 void MyMoneySchedule::setAutoEnter(bool autoenter)
217 {
218   Q_D(MyMoneySchedule);
219   d->m_autoEnter = autoenter;
220 }
221 
startDate() const222 QDate MyMoneySchedule::startDate() const
223 {
224   Q_D(const MyMoneySchedule);
225   if (d->m_startDate.isValid())
226     return d->m_startDate;
227   return nextDueDate();
228 }
229 
paymentType() const230 eMyMoney::Schedule::PaymentType MyMoneySchedule::paymentType() const
231 {
232   Q_D(const MyMoneySchedule);
233   return d->m_paymentType;
234 }
235 
236 /**
237   * Simple get method that returns true if the schedule is fixed.
238   *
239   * @return bool To indicate whether the instance is fixed.
240   */
isFixed() const241 bool MyMoneySchedule::isFixed() const
242 {
243   Q_D(const MyMoneySchedule);
244   return d->m_fixed;
245 }
246 
247 /**
248   * Simple get method that returns true if the schedule will end
249   * at some time.
250   *
251   * @return bool Indicates whether the instance will end.
252   */
willEnd() const253 bool MyMoneySchedule::willEnd() const
254 {
255   Q_D(const MyMoneySchedule);
256   return d->m_endDate.isValid();
257 }
258 
259 
nextDueDate() const260 QDate MyMoneySchedule::nextDueDate() const
261 {
262   Q_D(const MyMoneySchedule);
263   return d->m_transaction.postDate();
264 }
265 
adjustedNextDueDate() const266 QDate MyMoneySchedule::adjustedNextDueDate() const
267 {
268   if (isFinished())
269     return QDate();
270 
271   if (lastDayInMonth()) {
272     QDate date = nextDueDate();
273     return adjustedDate(QDate(date.year(), date.month(), date.daysInMonth()), weekendOption());
274   }
275 
276   return adjustedDate(nextDueDate(), weekendOption());
277 }
278 
adjustedDate(QDate date,Schedule::WeekendOption option) const279 QDate MyMoneySchedule::adjustedDate(QDate date, Schedule::WeekendOption option) const
280 {
281   if (!date.isValid() || option == Schedule::WeekendOption::MoveNothing || isProcessingDate(date))
282     return date;
283 
284   int step = 1;
285   if (option == Schedule::WeekendOption::MoveBefore)
286     step = -1;
287 
288   while (!isProcessingDate(date))
289     date = date.addDays(step);
290 
291   return date;
292 }
293 
setNextDueDate(const QDate & date)294 void MyMoneySchedule::setNextDueDate(const QDate& date)
295 {
296   Q_D(MyMoneySchedule);
297   if (date.isValid()) {
298     d->m_transaction.setPostDate(date);
299     // m_startDate = date;
300   }
301 }
302 
setLastPayment(const QDate & date)303 void MyMoneySchedule::setLastPayment(const QDate& date)
304 {
305   Q_D(MyMoneySchedule);
306   // Delete all payments older than date
307   QList<QDate>::Iterator it;
308   QList<QDate> delList;
309 
310   for (it = d->m_recordedPayments.begin(); it != d->m_recordedPayments.end(); ++it) {
311     if (*it < date || !date.isValid())
312       delList.append(*it);
313   }
314 
315   for (it = delList.begin(); it != delList.end(); ++it) {
316     d->m_recordedPayments.removeAll(*it);
317   }
318 
319   d->m_lastPayment = date;
320   if (!d->m_startDate.isValid())
321     d->m_startDate = date;
322 }
323 
name() const324 QString MyMoneySchedule::name() const
325 {
326   Q_D(const MyMoneySchedule);
327   return d->m_name;
328 }
329 
setName(const QString & nm)330 void MyMoneySchedule::setName(const QString& nm)
331 {
332   Q_D(MyMoneySchedule);
333   d->m_name = nm;
334 }
335 
weekendOption() const336 eMyMoney::Schedule::WeekendOption MyMoneySchedule::weekendOption() const
337 {
338   Q_D(const MyMoneySchedule);
339   return d->m_weekendOption;
340 }
341 
setOccurrence(Schedule::Occurrence occ)342 void MyMoneySchedule::setOccurrence(Schedule::Occurrence occ)
343 {
344   auto occ2 = occ;
345   auto mult = 1;
346   simpleToCompoundOccurrence(mult, occ2);
347   setOccurrencePeriod(occ2);
348   setOccurrenceMultiplier(mult);
349 }
350 
setOccurrencePeriod(Schedule::Occurrence occ)351 void MyMoneySchedule::setOccurrencePeriod(Schedule::Occurrence occ)
352 {
353   Q_D(MyMoneySchedule);
354   d->m_occurrence = occ;
355 }
356 
setOccurrenceMultiplier(int occmultiplier)357 void MyMoneySchedule::setOccurrenceMultiplier(int occmultiplier)
358 {
359   Q_D(MyMoneySchedule);
360   d->m_occurrenceMultiplier = occmultiplier < 1 ? 1 : occmultiplier;
361 }
362 
setType(Schedule::Type type)363 void MyMoneySchedule::setType(Schedule::Type type)
364 {
365   Q_D(MyMoneySchedule);
366   d->m_type = type;
367 }
368 
validate(bool id_check) const369 void MyMoneySchedule::validate(bool id_check) const
370 {
371   /* Check the supplied instance is valid...
372    *
373    * To be valid it must not have the id set and have the following fields set:
374    *
375    * m_occurrence
376    * m_type
377    * m_startDate
378    * m_paymentType
379    * m_transaction
380    *   the transaction must contain at least one split (two is better ;-)  )
381    */
382   Q_D(const MyMoneySchedule);
383   if (id_check && !d->m_id.isEmpty())
384     throw MYMONEYEXCEPTION_CSTRING("ID for schedule not empty when required");
385 
386   if (d->m_occurrence == Schedule::Occurrence::Any)
387     throw MYMONEYEXCEPTION_CSTRING("Invalid occurrence type for schedule");
388 
389   if (d->m_type == Schedule::Type::Any)
390     throw MYMONEYEXCEPTION_CSTRING("Invalid type for schedule");
391 
392   if (!nextDueDate().isValid())
393     throw MYMONEYEXCEPTION_CSTRING("Invalid next due date for schedule");
394 
395   if (d->m_paymentType == Schedule::PaymentType::Any)
396     throw MYMONEYEXCEPTION_CSTRING("Invalid payment type for schedule");
397 
398   if (d->m_transaction.splitCount() == 0)
399     throw MYMONEYEXCEPTION_CSTRING("Scheduled transaction does not contain splits");
400 
401   // Check the payment types
402   switch (d->m_type) {
403     case Schedule::Type::Bill:
404       if (d->m_paymentType == Schedule::PaymentType::DirectDeposit || d->m_paymentType == Schedule::PaymentType::ManualDeposit)
405         throw MYMONEYEXCEPTION_CSTRING("Invalid payment type for bills");
406       break;
407 
408     case Schedule::Type::Deposit:
409       if (d->m_paymentType == Schedule::PaymentType::DirectDebit || d->m_paymentType == Schedule::PaymentType::WriteChecque)
410         throw MYMONEYEXCEPTION_CSTRING("Invalid payment type for deposits");
411       break;
412 
413     case Schedule::Type::Any:
414       throw MYMONEYEXCEPTION_CSTRING("Invalid type ANY");
415       break;
416 
417     case Schedule::Type::Transfer:
418 //        if (m_paymentType == DirectDeposit || m_paymentType == ManualDeposit)
419 //          return false;
420       break;
421 
422     case Schedule::Type::LoanPayment:
423       break;
424   }
425 }
426 
adjustedNextPayment(const QDate & refDate) const427 QDate MyMoneySchedule::adjustedNextPayment(const QDate& refDate) const
428 {
429   return nextPaymentDate(true, refDate);
430 }
431 
adjustedNextPayment() const432 QDate MyMoneySchedule::adjustedNextPayment() const
433 {
434   return adjustedNextPayment(QDate::currentDate());
435 }
436 
nextPayment(const QDate & refDate) const437 QDate MyMoneySchedule::nextPayment(const QDate& refDate) const
438 {
439   return nextPaymentDate(false, refDate);
440 }
441 
nextPayment() const442 QDate MyMoneySchedule::nextPayment() const
443 {
444   return nextPayment(QDate::currentDate());
445 }
446 
nextPaymentDate(const bool & adjust,const QDate & refDate) const447 QDate MyMoneySchedule::nextPaymentDate(const bool& adjust, const QDate& refDate) const
448 {
449   Schedule::WeekendOption option(adjust ? weekendOption() :
450                         Schedule::WeekendOption::MoveNothing);
451 
452   Q_D(const MyMoneySchedule);
453   QDate adjEndDate(adjustedDate(d->m_endDate, option));
454 
455   // if the enddate is valid and it is before the reference date,
456   // then there will be no more payments.
457   if (adjEndDate.isValid() && adjEndDate < refDate) {
458     return QDate();
459   }
460 
461   QDate dueDate(nextDueDate());
462   QDate paymentDate(adjustedDate(dueDate, option));
463 
464   if (paymentDate.isValid() &&
465       (paymentDate <= refDate || d->m_recordedPayments.contains(dueDate))) {
466     switch (d->m_occurrence) {
467       case Schedule::Occurrence::Once:
468         // If the lastPayment is already set or the payment should have been
469         // prior to the reference date then invalidate the payment date.
470         if (d->m_lastPayment.isValid() || paymentDate <= refDate)
471           paymentDate = QDate();
472         break;
473 
474       case Schedule::Occurrence::Daily: {
475           int step = d->m_occurrenceMultiplier;
476           do {
477             dueDate = dueDate.addDays(step);
478             paymentDate = adjustedDate(dueDate, option);
479           } while (paymentDate.isValid() &&
480                    (paymentDate <= refDate ||
481                     d->m_recordedPayments.contains(dueDate)));
482         }
483         break;
484 
485       case Schedule::Occurrence::Weekly: {
486           int step = 7 * d->m_occurrenceMultiplier;
487           do {
488             dueDate = dueDate.addDays(step);
489             paymentDate = adjustedDate(dueDate, option);
490           } while (paymentDate.isValid() &&
491                    (paymentDate <= refDate ||
492                     d->m_recordedPayments.contains(dueDate)));
493         }
494         break;
495 
496       case Schedule::Occurrence::EveryHalfMonth:
497         do {
498           dueDate = addHalfMonths(dueDate, d->m_occurrenceMultiplier);
499           paymentDate = adjustedDate(dueDate, option);
500         } while (paymentDate.isValid() &&
501                  (paymentDate <= refDate ||
502                   d->m_recordedPayments.contains(dueDate)));
503         break;
504 
505       case Schedule::Occurrence::Monthly:
506         do {
507           dueDate = dueDate.addMonths(d->m_occurrenceMultiplier);
508           fixDate(dueDate);
509           paymentDate = adjustedDate(dueDate, option);
510         } while (paymentDate.isValid() &&
511                  (paymentDate <= refDate ||
512                   d->m_recordedPayments.contains(dueDate)));
513         break;
514 
515       case Schedule::Occurrence::Yearly:
516         do {
517           dueDate = dueDate.addYears(d->m_occurrenceMultiplier);
518           fixDate(dueDate);
519           paymentDate = adjustedDate(dueDate, option);
520         } while (paymentDate.isValid() &&
521                  (paymentDate <= refDate ||
522                   d->m_recordedPayments.contains(dueDate)));
523         break;
524 
525       case Schedule::Occurrence::Any:
526       default:
527         paymentDate = QDate();
528         break;
529     }
530   }
531   if (paymentDate.isValid() && adjEndDate.isValid() && paymentDate > adjEndDate)
532     paymentDate = QDate();
533 
534   return paymentDate;
535 }
536 
nextPaymentDate(const bool & adjust) const537 QDate MyMoneySchedule::nextPaymentDate(const bool& adjust) const
538 {
539   return nextPaymentDate(adjust, QDate::currentDate());
540 }
541 
paymentDates(const QDate & _startDate,const QDate & _endDate) const542 QList<QDate> MyMoneySchedule::paymentDates(const QDate& _startDate, const QDate& _endDate) const
543 {
544   QDate paymentDate(nextDueDate());
545   QList<QDate> theDates;
546 
547   Schedule::WeekendOption option(weekendOption());
548 
549   Q_D(const MyMoneySchedule);
550   QDate endDate(_endDate);
551   if (willEnd() && d->m_endDate < endDate) {
552     // consider the adjusted end date instead of the plain end date
553     endDate = adjustedDate(d->m_endDate, option);
554   }
555 
556   QDate start_date(adjustedDate(startDate(), option));
557   // if the period specified by the parameters and the adjusted period
558   // defined for this schedule don't overlap, then the list remains empty
559   if ((willEnd() && adjustedDate(d->m_endDate, option) < _startDate)
560       || start_date > endDate)
561     return theDates;
562 
563   QDate date(adjustedDate(paymentDate, option));
564 
565   switch (d->m_occurrence) {
566     case Schedule::Occurrence::Once:
567       if (start_date >= _startDate && start_date <= endDate)
568         theDates.append(start_date);
569       break;
570 
571     case Schedule::Occurrence::Daily:
572       while (date.isValid() && (date <= endDate)) {
573         if (date >= _startDate)
574           theDates.append(date);
575         paymentDate = paymentDate.addDays(d->m_occurrenceMultiplier);
576         date = adjustedDate(paymentDate, option);
577       }
578       break;
579 
580     case Schedule::Occurrence::Weekly: {
581         int step = 7 * d->m_occurrenceMultiplier;
582         while (date.isValid() && (date <= endDate)) {
583           if (date >= _startDate)
584             theDates.append(date);
585           paymentDate = paymentDate.addDays(step);
586           date = adjustedDate(paymentDate, option);
587         }
588       }
589       break;
590 
591     case Schedule::Occurrence::EveryHalfMonth:
592       while (date.isValid() && (date <= endDate)) {
593         if (date >= _startDate)
594           theDates.append(date);
595         paymentDate = addHalfMonths(paymentDate, d->m_occurrenceMultiplier);
596         date = adjustedDate(paymentDate, option);
597       }
598       break;
599 
600     case Schedule::Occurrence::Monthly:
601       while (date.isValid() && (date <= endDate)) {
602         if (date >= _startDate)
603           theDates.append(date);
604         paymentDate = paymentDate.addMonths(d->m_occurrenceMultiplier);
605         fixDate(paymentDate);
606         date = adjustedDate(paymentDate, option);
607       }
608       break;
609 
610     case Schedule::Occurrence::Yearly:
611       while (date.isValid() && (date <= endDate)) {
612         if (date >= _startDate)
613           theDates.append(date);
614         paymentDate = paymentDate.addYears(d->m_occurrenceMultiplier);
615         fixDate(paymentDate);
616         date = adjustedDate(paymentDate, option);
617       }
618       break;
619 
620     case Schedule::Occurrence::Any:
621     default:
622       break;
623   }
624 
625   return theDates;
626 }
627 
operator <(const MyMoneySchedule & right) const628 bool MyMoneySchedule::operator <(const MyMoneySchedule& right) const
629 {
630   return adjustedNextDueDate() < right.adjustedNextDueDate();
631 }
632 
operator ==(const MyMoneySchedule & right) const633 bool MyMoneySchedule::operator ==(const MyMoneySchedule& right) const
634 {
635   Q_D(const MyMoneySchedule);
636   auto d2 = static_cast<const MyMoneySchedulePrivate *>(right.d_func());
637   if (MyMoneyObject::operator==(right) &&
638       d->m_occurrence == d2->m_occurrence &&
639       d->m_occurrenceMultiplier == d2->m_occurrenceMultiplier &&
640       d->m_type == d2->m_type &&
641       d->m_startDate == d2->m_startDate &&
642       d->m_paymentType == d2->m_paymentType &&
643       d->m_fixed == d2->m_fixed &&
644       d->m_transaction == d2->m_transaction &&
645       d->m_endDate == d2->m_endDate &&
646       d->m_lastDayInMonth == d2->m_lastDayInMonth &&
647       d->m_autoEnter == d2->m_autoEnter &&
648       d->m_lastPayment == d2->m_lastPayment &&
649       ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)))
650     return true;
651   return false;
652 }
653 
operator !=(const MyMoneySchedule & right) const654 bool MyMoneySchedule::operator !=(const MyMoneySchedule& right) const
655 {
656   return ! operator==(right);
657 }
658 
transactionsRemaining() const659 int MyMoneySchedule::transactionsRemaining() const
660 {
661   Q_D(const MyMoneySchedule);
662   return transactionsRemainingUntil(adjustedDate(d->m_endDate, weekendOption()));
663 }
664 
transactionsRemainingUntil(const QDate & endDate) const665 int MyMoneySchedule::transactionsRemainingUntil(const QDate& endDate) const
666 {
667   auto counter = 0;
668   Q_D(const MyMoneySchedule);
669 
670   const auto beginDate = d->m_lastPayment.isValid() ? d->m_lastPayment : startDate();
671   if (beginDate.isValid() && endDate.isValid()) {
672     QList<QDate> dates = paymentDates(beginDate, endDate);
673     counter = dates.count();
674   }
675   return counter;
676 }
677 
endDate() const678 QDate MyMoneySchedule::endDate() const
679 {
680   Q_D(const MyMoneySchedule);
681   return d->m_endDate;
682 }
683 
autoEnter() const684 bool MyMoneySchedule::autoEnter() const
685 {
686   Q_D(const MyMoneySchedule);
687   return d->m_autoEnter;
688 }
689 
lastDayInMonth() const690 bool MyMoneySchedule::lastDayInMonth() const
691 {
692   Q_D(const MyMoneySchedule);
693   return d->m_lastDayInMonth;
694 }
695 
transaction() const696 MyMoneyTransaction MyMoneySchedule::transaction() const
697 {
698   Q_D(const MyMoneySchedule);
699   return d->m_transaction;
700 }
701 
lastPayment() const702 QDate MyMoneySchedule::lastPayment() const
703 {
704   Q_D(const MyMoneySchedule);
705   return d->m_lastPayment;
706 }
707 
account(int cnt) const708 MyMoneyAccount MyMoneySchedule::account(int cnt) const
709 {
710   Q_D(const MyMoneySchedule);
711   QList<MyMoneySplit> splits = d->m_transaction.splits();
712   QList<MyMoneySplit>::ConstIterator it;
713   auto file = MyMoneyFile::instance();
714   MyMoneyAccount acc;
715 
716   // search the first asset or liability account
717   for (it = splits.constBegin(); it != splits.constEnd() && (acc.id().isEmpty() || cnt); ++it) {
718     try {
719       acc = file->account((*it).accountId());
720       if (acc.isAssetLiability())
721         --cnt;
722 
723       if (!cnt)
724         return acc;
725     } catch (const MyMoneyException &) {
726       qWarning("Schedule '%s' references unknown account '%s'", qPrintable(id()),   qPrintable((*it).accountId()));
727       return MyMoneyAccount();
728     }
729   }
730 
731   return MyMoneyAccount();
732 }
733 
transferAccount() const734 MyMoneyAccount MyMoneySchedule::transferAccount() const {
735   return account(2);
736 }
737 
dateAfter(int transactions) const738 QDate MyMoneySchedule::dateAfter(int transactions) const
739 {
740   auto counter = 1;
741   QDate paymentDate(startDate());
742 
743   if (transactions <= 0)
744     return paymentDate;
745 
746   Q_D(const MyMoneySchedule);
747   switch (d->m_occurrence) {
748     case Schedule::Occurrence::Once:
749       break;
750 
751     case Schedule::Occurrence::Daily:
752       while (counter++ < transactions)
753         paymentDate = paymentDate.addDays(d->m_occurrenceMultiplier);
754       break;
755 
756     case Schedule::Occurrence::Weekly: {
757         int step = 7 * d->m_occurrenceMultiplier;
758         while (counter++ < transactions)
759           paymentDate = paymentDate.addDays(step);
760       }
761       break;
762 
763     case Schedule::Occurrence::EveryHalfMonth:
764       paymentDate = addHalfMonths(paymentDate, d->m_occurrenceMultiplier * (transactions - 1));
765       break;
766 
767     case Schedule::Occurrence::Monthly:
768       while (counter++ < transactions)
769         paymentDate = paymentDate.addMonths(d->m_occurrenceMultiplier);
770       break;
771 
772     case Schedule::Occurrence::Yearly:
773       while (counter++ < transactions)
774         paymentDate = paymentDate.addYears(d->m_occurrenceMultiplier);
775       break;
776 
777     case Schedule::Occurrence::Any:
778     default:
779       break;
780   }
781 
782   return paymentDate;
783 }
784 
isOverdue() const785 bool MyMoneySchedule::isOverdue() const
786 {
787   if (isFinished())
788     return false;
789 
790   if (adjustedNextDueDate() >= QDate::currentDate())
791     return false;
792 
793   return true;
794 }
795 
isFinished() const796 bool MyMoneySchedule::isFinished() const
797 {
798   Q_D(const MyMoneySchedule);
799   if (!d->m_lastPayment.isValid())
800     return false;
801 
802   if (d->m_endDate.isValid()) {
803     if (d->m_lastPayment >= d->m_endDate
804         || !nextDueDate().isValid()
805         || nextDueDate() > d->m_endDate)
806       return true;
807   }
808 
809   // Check to see if its a once off payment
810   if (d->m_occurrence == Schedule::Occurrence::Once)
811     return true;
812 
813   return false;
814 }
815 
hasRecordedPayment(const QDate & date) const816 bool MyMoneySchedule::hasRecordedPayment(const QDate& date) const
817 {
818   Q_D(const MyMoneySchedule);
819   // m_lastPayment should always be > recordedPayments()
820   if (d->m_lastPayment.isValid() && d->m_lastPayment >= date)
821     return true;
822 
823   if (d->m_recordedPayments.contains(date))
824     return true;
825 
826   return false;
827 }
828 
recordPayment(const QDate & date)829 void MyMoneySchedule::recordPayment(const QDate& date)
830 {
831   Q_D(MyMoneySchedule);
832   d->m_recordedPayments.append(date);
833 }
834 
recordedPayments() const835 QList<QDate> MyMoneySchedule::recordedPayments() const
836 {
837   Q_D(const MyMoneySchedule);
838   return d->m_recordedPayments;
839 }
840 
setWeekendOption(const Schedule::WeekendOption option)841 void MyMoneySchedule::setWeekendOption(const Schedule::WeekendOption option)
842 {
843   Q_D(MyMoneySchedule);
844   // make sure only valid values are used. Invalid defaults to MoveNothing.
845   switch (option) {
846     case Schedule::WeekendOption::MoveBefore:
847     case Schedule::WeekendOption::MoveAfter:
848       d->m_weekendOption = option;
849       break;
850 
851     default:
852       d->m_weekendOption = Schedule::WeekendOption::MoveNothing;
853       break;
854   }
855 }
856 
fixDate(QDate & date) const857 void MyMoneySchedule::fixDate(QDate& date) const
858 {
859   Q_D(const MyMoneySchedule);
860   QDate fixDate(d->m_startDate);
861   if (fixDate.isValid()
862       && date.day() != fixDate.day()
863       && QDate::isValid(date.year(), date.month(), fixDate.day())) {
864     date = QDate(date.year(), date.month(), fixDate.day());
865   }
866 }
867 
hasReferenceTo(const QString & id) const868 bool MyMoneySchedule::hasReferenceTo(const QString& id) const
869 {
870   Q_D(const MyMoneySchedule);
871   return d->m_transaction.hasReferenceTo(id);
872 }
873 
occurrenceToString() const874 QString MyMoneySchedule::occurrenceToString() const
875 {
876   return occurrenceToString(occurrenceMultiplier(), occurrence());
877 }
878 
occurrenceToString(Schedule::Occurrence occurrence)879 QString MyMoneySchedule::occurrenceToString(Schedule::Occurrence occurrence)
880 {
881   QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any");
882 
883   if (occurrence == Schedule::Occurrence::Once)
884     occurrenceString = I18N_NOOP2("Frequency of schedule", "Once");
885   else if (occurrence == Schedule::Occurrence::Daily)
886     occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily");
887   else if (occurrence == Schedule::Occurrence::Weekly)
888     occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly");
889   else if (occurrence == Schedule::Occurrence::Fortnightly)
890     occurrenceString = I18N_NOOP2("Frequency of schedule", "Fortnightly");
891   else if (occurrence == Schedule::Occurrence::EveryOtherWeek)
892     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week");
893   else if (occurrence == Schedule::Occurrence::EveryHalfMonth)
894     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month");
895   else if (occurrence == Schedule::Occurrence::EveryThreeWeeks)
896     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks");
897   else if (occurrence == Schedule::Occurrence::EveryFourWeeks)
898     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks");
899   else if (occurrence == Schedule::Occurrence::EveryThirtyDays)
900     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days");
901   else if (occurrence == Schedule::Occurrence::Monthly)
902     occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly");
903   else if (occurrence == Schedule::Occurrence::EveryEightWeeks)
904     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks");
905   else if (occurrence == Schedule::Occurrence::EveryOtherMonth)
906     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months");
907   else if (occurrence == Schedule::Occurrence::EveryThreeMonths)
908     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months");
909   else if (occurrence == Schedule::Occurrence::Quarterly)
910     occurrenceString = I18N_NOOP2("Frequency of schedule", "Quarterly");
911   else if (occurrence == Schedule::Occurrence::EveryFourMonths)
912     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months");
913   else if (occurrence == Schedule::Occurrence::TwiceYearly)
914     occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly");
915   else if (occurrence == Schedule::Occurrence::Yearly)
916     occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly");
917   else if (occurrence == Schedule::Occurrence::EveryOtherYear)
918     occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year");
919   return occurrenceString;
920 }
921 
occurrenceToString(int mult,Schedule::Occurrence type)922 QString MyMoneySchedule::occurrenceToString(int mult, Schedule::Occurrence type)
923 {
924   QString occurrenceString = I18N_NOOP2("Frequency of schedule", "Any");
925 
926   if (type == Schedule::Occurrence::Once)
927     switch (mult) {
928       case 1:
929         occurrenceString = I18N_NOOP2("Frequency of schedule", "Once");
930         break;
931       default:
932         occurrenceString = I18N_NOOP2("Frequency of schedule", QString("%1 times").arg(mult));
933     }
934   else if (type == Schedule::Occurrence::Daily)
935     switch (mult) {
936       case 1:
937         occurrenceString = I18N_NOOP2("Frequency of schedule", "Daily");
938         break;
939       case 30:
940         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every thirty days");
941         break;
942       default:
943         occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 days").arg(mult));
944     }
945   else if (type == Schedule::Occurrence::Weekly)
946     switch (mult) {
947       case 1:
948         occurrenceString = I18N_NOOP2("Frequency of schedule", "Weekly");
949         break;
950       case 2:
951         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other week");
952         break;
953       case 3:
954         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three weeks");
955         break;
956       case 4:
957         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four weeks");
958         break;
959       case 8:
960         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every eight weeks");
961         break;
962       default:
963         occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 weeks").arg(mult));
964     }
965   else if (type == Schedule::Occurrence::EveryHalfMonth)
966     switch (mult) {
967       case 1:
968         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every half month");
969         break;
970       default:
971         occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 half months").arg(mult));
972     }
973   else if (type == Schedule::Occurrence::Monthly)
974     switch (mult) {
975       case 1:
976         occurrenceString = I18N_NOOP2("Frequency of schedule", "Monthly");
977         break;
978       case 2:
979         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every two months");
980         break;
981       case 3:
982         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every three months");
983         break;
984       case 4:
985         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every four months");
986         break;
987       case 6:
988         occurrenceString = I18N_NOOP2("Frequency of schedule", "Twice yearly");
989         break;
990       default:
991         occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 months").arg(mult));
992     }
993   else if (type == Schedule::Occurrence::Yearly)
994     switch (mult) {
995       case 1:
996         occurrenceString = I18N_NOOP2("Frequency of schedule", "Yearly");
997         break;
998       case 2:
999         occurrenceString = I18N_NOOP2("Frequency of schedule", "Every other year");
1000         break;
1001       default:
1002         occurrenceString = I18N_NOOP2("Frequency of schedule", QString("Every %1 years").arg(mult));
1003     }
1004   return occurrenceString;
1005 }
1006 
occurrencePeriodToString(Schedule::Occurrence type)1007 QString MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence type)
1008 {
1009   QString occurrenceString = I18N_NOOP2("Schedule occurrence period", "Any");
1010 
1011   if (type == Schedule::Occurrence::Once)
1012     occurrenceString = I18N_NOOP2("Schedule occurrence period", "Once");
1013   else if (type == Schedule::Occurrence::Daily)
1014     occurrenceString = I18N_NOOP2("Schedule occurrence period", "Day");
1015   else if (type == Schedule::Occurrence::Weekly)
1016     occurrenceString = I18N_NOOP2("Schedule occurrence period", "Week");
1017   else if (type == Schedule::Occurrence::EveryHalfMonth)
1018     occurrenceString = I18N_NOOP2("Schedule occurrence period", "Half-month");
1019   else if (type == Schedule::Occurrence::Monthly)
1020     occurrenceString = I18N_NOOP2("Schedule occurrence period", "Month");
1021   else if (type == Schedule::Occurrence::Yearly)
1022     occurrenceString = I18N_NOOP2("Schedule occurrence period", "Year");
1023   return occurrenceString;
1024 }
1025 
scheduleTypeToString(Schedule::Type type)1026 QString MyMoneySchedule::scheduleTypeToString(Schedule::Type type)
1027 {
1028   QString text;
1029 
1030   switch (type) {
1031     case Schedule::Type::Bill:
1032       text = I18N_NOOP2("Scheduled transaction type", "Bill");
1033       break;
1034     case Schedule::Type::Deposit:
1035       text = I18N_NOOP2("Scheduled transaction type", "Deposit");
1036       break;
1037     case Schedule::Type::Transfer:
1038       text = I18N_NOOP2("Scheduled transaction type", "Transfer");
1039       break;
1040     case Schedule::Type::LoanPayment:
1041       text = I18N_NOOP2("Scheduled transaction type", "Loan payment");
1042       break;
1043     case Schedule::Type::Any:
1044     default:
1045       text = I18N_NOOP2("Scheduled transaction type", "Unknown");
1046   }
1047   return text;
1048 }
1049 
1050 
paymentMethodToString(Schedule::PaymentType paymentType)1051 QString MyMoneySchedule::paymentMethodToString(Schedule::PaymentType paymentType)
1052 {
1053   QString text;
1054 
1055   switch (paymentType) {
1056     case Schedule::PaymentType::DirectDebit:
1057       text = I18N_NOOP2("Scheduled Transaction payment type", "Direct debit");
1058       break;
1059     case Schedule::PaymentType::DirectDeposit:
1060       text = I18N_NOOP2("Scheduled Transaction payment type", "Direct deposit");
1061       break;
1062     case Schedule::PaymentType::ManualDeposit:
1063       text = I18N_NOOP2("Scheduled Transaction payment type", "Manual deposit");
1064       break;
1065     case Schedule::PaymentType::Other:
1066       text = I18N_NOOP2("Scheduled Transaction payment type", "Other");
1067       break;
1068     case Schedule::PaymentType::WriteChecque:
1069       text = I18N_NOOP2("Scheduled Transaction payment type", "Write check");
1070       break;
1071     case Schedule::PaymentType::StandingOrder:
1072       text = I18N_NOOP2("Scheduled Transaction payment type", "Standing order");
1073       break;
1074     case Schedule::PaymentType::BankTransfer:
1075       text = I18N_NOOP2("Scheduled Transaction payment type", "Bank transfer");
1076       break;
1077     case Schedule::PaymentType::Any:
1078       text = I18N_NOOP2("Scheduled Transaction payment type", "Any (Error)");
1079       break;
1080   }
1081   return text;
1082 }
1083 
weekendOptionToString(Schedule::WeekendOption weekendOption)1084 QString MyMoneySchedule::weekendOptionToString(Schedule::WeekendOption weekendOption)
1085 {
1086   QString text;
1087 
1088   switch (weekendOption) {
1089     case Schedule::WeekendOption::MoveBefore:
1090       text = I18N_NOOP("Change the date to the previous processing day");
1091       break;
1092     case Schedule::WeekendOption::MoveAfter:
1093       text = I18N_NOOP("Change the date to the next processing day");
1094       break;
1095     case Schedule::WeekendOption::MoveNothing:
1096       text = I18N_NOOP("Do not change the date");
1097       break;
1098   }
1099   return text;
1100 }
1101 
1102 // until we don't have the means to store the value
1103 // of the variation, we default to 10% in case this
1104 // scheduled transaction is marked 'not fixed'.
1105 //
1106 // ipwizard 2009-04-18
1107 
variation() const1108 int MyMoneySchedule::variation() const
1109 {
1110   int rc = 0;
1111   if (!isFixed()) {
1112     rc = 10;
1113 #if 0
1114     QString var = value("kmm-variation");
1115     if (!var.isEmpty())
1116       rc = var.toInt();
1117 #endif
1118   }
1119   return rc;
1120 }
1121 
setVariation(int var)1122 void MyMoneySchedule::setVariation(int var)
1123 {
1124   Q_UNUSED(var)
1125 #if 0
1126   deletePair("kmm-variation");
1127   if (var != 0)
1128     setValue("kmm-variation", QString("%1").arg(var));
1129 #endif
1130 }
1131 
eventsPerYear(Schedule::Occurrence occurrence)1132 int MyMoneySchedule::eventsPerYear(Schedule::Occurrence occurrence)
1133 {
1134   int rc = 0;
1135 
1136   switch (occurrence) {
1137     case Schedule::Occurrence::Daily:
1138       rc = 365;
1139       break;
1140     case Schedule::Occurrence::Weekly:
1141       rc = 52;
1142       break;
1143     case Schedule::Occurrence::Fortnightly:
1144       rc = 26;
1145       break;
1146     case Schedule::Occurrence::EveryOtherWeek:
1147       rc = 26;
1148       break;
1149     case Schedule::Occurrence::EveryHalfMonth:
1150       rc = 24;
1151       break;
1152     case Schedule::Occurrence::EveryThreeWeeks:
1153       rc = 17;
1154       break;
1155     case Schedule::Occurrence::EveryFourWeeks:
1156       rc = 13;
1157       break;
1158     case Schedule::Occurrence::Monthly:
1159     case Schedule::Occurrence::EveryThirtyDays:
1160       rc = 12;
1161       break;
1162     case Schedule::Occurrence::EveryEightWeeks:
1163       rc = 6;
1164       break;
1165     case Schedule::Occurrence::EveryOtherMonth:
1166       rc = 6;
1167       break;
1168     case Schedule::Occurrence::EveryThreeMonths:
1169     case Schedule::Occurrence::Quarterly:
1170       rc = 4;
1171       break;
1172     case Schedule::Occurrence::EveryFourMonths:
1173       rc = 3;
1174       break;
1175     case Schedule::Occurrence::TwiceYearly:
1176       rc = 2;
1177       break;
1178     case Schedule::Occurrence::Yearly:
1179       rc = 1;
1180       break;
1181     default:
1182       qWarning("Occurrence not supported by financial calculator");
1183   }
1184 
1185   return rc;
1186 }
1187 
daysBetweenEvents(Schedule::Occurrence occurrence)1188 int MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence occurrence)
1189 {
1190   int rc = 0;
1191 
1192   switch (occurrence) {
1193     case Schedule::Occurrence::Daily:
1194       rc = 1;
1195       break;
1196     case Schedule::Occurrence::Weekly:
1197       rc = 7;
1198       break;
1199     case Schedule::Occurrence::Fortnightly:
1200       rc = 14;
1201       break;
1202     case Schedule::Occurrence::EveryOtherWeek:
1203       rc = 14;
1204       break;
1205     case Schedule::Occurrence::EveryHalfMonth:
1206       rc = 15;
1207       break;
1208     case Schedule::Occurrence::EveryThreeWeeks:
1209       rc = 21;
1210       break;
1211     case Schedule::Occurrence::EveryFourWeeks:
1212       rc = 28;
1213       break;
1214     case Schedule::Occurrence::EveryThirtyDays:
1215       rc = 30;
1216       break;
1217     case Schedule::Occurrence::Monthly:
1218       rc = 30;
1219       break;
1220     case Schedule::Occurrence::EveryEightWeeks:
1221       rc = 56;
1222       break;
1223     case Schedule::Occurrence::EveryOtherMonth:
1224       rc = 60;
1225       break;
1226     case Schedule::Occurrence::EveryThreeMonths:
1227     case Schedule::Occurrence::Quarterly:
1228       rc = 90;
1229       break;
1230     case Schedule::Occurrence::EveryFourMonths:
1231       rc = 120;
1232       break;
1233     case Schedule::Occurrence::TwiceYearly:
1234       rc = 180;
1235       break;
1236     case Schedule::Occurrence::Yearly:
1237       rc = 360;
1238       break;
1239     default:
1240       qWarning("Occurrence not supported by financial calculator");
1241   }
1242 
1243   return rc;
1244 }
1245 
addHalfMonths(QDate date,int mult) const1246 QDate MyMoneySchedule::addHalfMonths(QDate date, int mult) const
1247 {
1248   QDate newdate = date;
1249   int d, dm;
1250   if (mult > 0) {
1251     d = newdate.day();
1252     if (d <= 12) {
1253       if (mult % 2 == 0)
1254         newdate = newdate.addMonths(mult >> 1);
1255       else
1256         newdate = newdate.addMonths(mult >> 1).addDays(15);
1257     } else
1258       for (int i = 0; i < mult; i++) {
1259         if (d <= 13)
1260           newdate = newdate.addDays(15);
1261         else {
1262           dm = newdate.daysInMonth();
1263           if (d == 14)
1264             newdate = newdate.addDays((dm < 30) ? dm - d : 15);
1265           else if (d == 15)
1266             newdate = newdate.addDays(dm - d);
1267           else if (d == dm)
1268             newdate = newdate.addDays(15 - d).addMonths(1);
1269           else
1270             newdate = newdate.addDays(-15).addMonths(1);
1271         }
1272         d = newdate.day();
1273       }
1274   } else if (mult < 0)  // Go backwards
1275     for (int i = 0; i > mult; i--) {
1276       d = newdate.day();
1277       dm = newdate.daysInMonth();
1278       if (d > 15) {
1279         dm = newdate.daysInMonth();
1280         newdate = newdate.addDays((d == dm) ? 15 - dm : -15);
1281       } else if (d <= 13)
1282         newdate = newdate.addMonths(-1).addDays(15);
1283       else if (d == 15)
1284         newdate = newdate.addDays(-15);
1285       else { // 14
1286         newdate = newdate.addMonths(-1);
1287         dm = newdate.daysInMonth();
1288         newdate = newdate.addDays((dm < 30) ? dm - d : 15);
1289       }
1290     }
1291   return newdate;
1292 }
1293 
1294 /**
1295   * Helper method to convert simple occurrence to compound occurrence + multiplier
1296   *
1297   * @param multiplier Returned by reference.  Adjusted multiplier
1298   * @param occurrence Returned by reference.  Occurrence type
1299   */
simpleToCompoundOccurrence(int & multiplier,Schedule::Occurrence & occurrence)1300 void MyMoneySchedule::simpleToCompoundOccurrence(int& multiplier, Schedule::Occurrence& occurrence)
1301 {
1302   Schedule::Occurrence newOcc = occurrence;
1303   int newMulti = 1;
1304   if (occurrence == Schedule::Occurrence::Once ||
1305       occurrence == Schedule::Occurrence::Daily ||
1306       occurrence == Schedule::Occurrence::Weekly ||
1307       occurrence == Schedule::Occurrence::EveryHalfMonth ||
1308       occurrence == Schedule::Occurrence::Monthly ||
1309       occurrence == Schedule::Occurrence::Yearly) { // Already a base occurrence and multiplier
1310   } else if (occurrence == Schedule::Occurrence::Fortnightly ||
1311              occurrence == Schedule::Occurrence::EveryOtherWeek) {
1312     newOcc    = Schedule::Occurrence::Weekly;
1313     newMulti  = 2;
1314   } else if (occurrence == Schedule::Occurrence::EveryThreeWeeks) {
1315     newOcc    = Schedule::Occurrence::Weekly;
1316     newMulti  = 3;
1317   } else if (occurrence == Schedule::Occurrence::EveryFourWeeks) {
1318     newOcc    = Schedule::Occurrence::Weekly;
1319     newMulti  = 4;
1320   } else if (occurrence == Schedule::Occurrence::EveryThirtyDays) {
1321     newOcc    = Schedule::Occurrence::Daily;
1322     newMulti  = 30;
1323   } else if (occurrence == Schedule::Occurrence::EveryEightWeeks) {
1324     newOcc    = Schedule::Occurrence::Weekly;
1325     newMulti  = 8;
1326   } else if (occurrence == Schedule::Occurrence::EveryOtherMonth) {
1327     newOcc    = Schedule::Occurrence::Monthly;
1328     newMulti  = 2;
1329   } else if (occurrence == Schedule::Occurrence::EveryThreeMonths ||
1330              occurrence == Schedule::Occurrence::Quarterly) {
1331     newOcc    = Schedule::Occurrence::Monthly;
1332     newMulti  = 3;
1333   } else if (occurrence == Schedule::Occurrence::EveryFourMonths) {
1334     newOcc    = Schedule::Occurrence::Monthly;
1335     newMulti  = 4;
1336   } else if (occurrence == Schedule::Occurrence::TwiceYearly) {
1337     newOcc    = Schedule::Occurrence::Monthly;
1338     newMulti  = 6;
1339   } else if (occurrence == Schedule::Occurrence::EveryOtherYear) {
1340     newOcc    = Schedule::Occurrence::Yearly;
1341     newMulti  = 2;
1342   } else { // Unknown
1343     newOcc    = Schedule::Occurrence::Any;
1344     newMulti  = 1;
1345   }
1346   if (newOcc != occurrence) {
1347     occurrence   = newOcc;
1348     multiplier  = newMulti == 1 ? multiplier : newMulti * multiplier;
1349   }
1350 }
1351 
1352 /**
1353   * Helper method to convert compound occurrence + multiplier to simple occurrence
1354   *
1355   * @param multiplier Returned by reference.  Adjusted multiplier
1356   * @param occurrence Returned by reference.  Occurrence type
1357   */
compoundToSimpleOccurrence(int & multiplier,Schedule::Occurrence & occurrence)1358 void MyMoneySchedule::compoundToSimpleOccurrence(int& multiplier, Schedule::Occurrence& occurrence)
1359 {
1360   Schedule::Occurrence newOcc = occurrence;
1361   if (occurrence == Schedule::Occurrence::Once) { // Nothing to do
1362   } else if (occurrence == Schedule::Occurrence::Daily) {
1363     switch (multiplier) {
1364       case 1:
1365         break;
1366       case 30:
1367         newOcc = Schedule::Occurrence::EveryThirtyDays;
1368         break;
1369     }
1370   } else if (newOcc == Schedule::Occurrence::Weekly) {
1371     switch (multiplier) {
1372       case 1:
1373         break;
1374       case 2:
1375         newOcc = Schedule::Occurrence::EveryOtherWeek;
1376         break;
1377       case 3:
1378         newOcc = Schedule::Occurrence::EveryThreeWeeks;
1379         break;
1380       case 4:
1381         newOcc = Schedule::Occurrence::EveryFourWeeks;
1382         break;
1383       case 8:
1384         newOcc = Schedule::Occurrence::EveryEightWeeks;
1385         break;
1386     }
1387   } else if (occurrence == Schedule::Occurrence::Monthly)
1388     switch (multiplier) {
1389       case 1:
1390         break;
1391       case 2:
1392         newOcc = Schedule::Occurrence::EveryOtherMonth;
1393         break;
1394       case 3:
1395         newOcc = Schedule::Occurrence::EveryThreeMonths;
1396         break;
1397       case 4:
1398         newOcc = Schedule::Occurrence::EveryFourMonths;
1399         break;
1400       case 6:
1401         newOcc = Schedule::Occurrence::TwiceYearly;
1402         break;
1403     }
1404   else if (occurrence == Schedule::Occurrence::EveryHalfMonth)
1405     switch (multiplier) {
1406       case 1:
1407         break;
1408     }
1409   else if (occurrence == Schedule::Occurrence::Yearly) {
1410     switch (multiplier) {
1411       case 1:
1412         break;
1413       case 2:
1414         newOcc = Schedule::Occurrence::EveryOtherYear;
1415         break;
1416     }
1417   }
1418   if (occurrence != newOcc) { // Changed to derived type
1419     occurrence = newOcc;
1420     multiplier = 1;
1421   }
1422 }
1423 
setProcessingCalendar(IMyMoneyProcessingCalendar * pc)1424 void MyMoneySchedule::setProcessingCalendar(IMyMoneyProcessingCalendar* pc)
1425 {
1426   processingCalendarPtr = pc;
1427 }
1428 
isProcessingDate(const QDate & date) const1429 bool MyMoneySchedule::isProcessingDate(const QDate& date) const
1430 {
1431   if (processingCalendarPtr)
1432     return processingCalendarPtr->isProcessingDate(date);
1433 
1434   /// @todo test against m_processingDays instead?  (currently only for tests)
1435   return date.dayOfWeek() < Qt::Saturday;
1436 }
1437 
processingCalendar() const1438 IMyMoneyProcessingCalendar* MyMoneySchedule::processingCalendar() const
1439 {
1440   return processingCalendarPtr;
1441 }
1442 
replaceId(const QString & newId,const QString & oldId)1443 bool MyMoneySchedule::replaceId(const QString& newId, const QString& oldId)
1444 {
1445   Q_D(MyMoneySchedule);
1446   return d->m_transaction.replaceId(newId, oldId);
1447 }
1448