1 /*
2  * Copyright 2006       Ace Jones <acejones@users.sourceforge.net>
3  * Copyright 2006       Darren Gould <darren_gould@gmx.de>
4  * Copyright 2017       Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
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 "mymoneybudget.h"
21 #include "mymoneybudget_p.h"
22 
23 // ----------------------------------------------------------------------------
24 // QT Includes
25 
26 #include <QMap>
27 
28 // ----------------------------------------------------------------------------
29 // KDE Includes
30 
31 // ----------------------------------------------------------------------------
32 // Project Includes
33 
34 class MyMoneyBudget::PeriodGroupPrivate
35 {
36 public:
37   QDate         m_start;
38   MyMoneyMoney  m_amount;
39 };
40 
PeriodGroup()41 MyMoneyBudget::PeriodGroup::PeriodGroup() :
42   d_ptr(new PeriodGroupPrivate)
43 {
44 }
45 
PeriodGroup(const MyMoneyBudget::PeriodGroup & other)46 MyMoneyBudget::PeriodGroup::PeriodGroup(const MyMoneyBudget::PeriodGroup & other) :
47   d_ptr(new PeriodGroupPrivate(*other.d_func()))
48 {
49 }
50 
~PeriodGroup()51 MyMoneyBudget::PeriodGroup::~PeriodGroup()
52 {
53   Q_D(PeriodGroup);
54   delete d;
55 }
56 
startDate() const57 QDate MyMoneyBudget::PeriodGroup::startDate() const
58 {
59   Q_D(const PeriodGroup);
60   return d->m_start;
61 }
62 
setStartDate(const QDate & start)63 void MyMoneyBudget::PeriodGroup::setStartDate(const QDate& start)
64 {
65   Q_D(PeriodGroup);
66   d->m_start  = start;
67 }
68 
amount() const69 MyMoneyMoney MyMoneyBudget::PeriodGroup::amount() const
70 {
71   Q_D(const PeriodGroup);
72   return d->m_amount;
73 }
74 
setAmount(const MyMoneyMoney & amount)75 void MyMoneyBudget::PeriodGroup::setAmount(const MyMoneyMoney& amount)
76 {
77   Q_D(PeriodGroup);
78   d->m_amount = amount;
79 }
80 
operator ==(const PeriodGroup & right) const81 bool MyMoneyBudget::PeriodGroup::operator == (const PeriodGroup& right) const
82 {
83   Q_D(const PeriodGroup);
84   auto d2 = static_cast<const PeriodGroupPrivate *>(right.d_func());
85   return (d->m_start == d2->m_start && d->m_amount == d2->m_amount);
86 }
87 
88 class MyMoneyBudget::AccountGroupPrivate {
89 
90 public:
91 
AccountGroupPrivate()92   AccountGroupPrivate() :
93     m_budgetlevel(eMyMoney::Budget::Level::None),
94     m_budgetsubaccounts(false)
95   {
96   }
97 
98   QString                                   m_id;
99   eMyMoney::Budget::Level                   m_budgetlevel;
100   bool                                      m_budgetsubaccounts;
101   QMap<QDate, MyMoneyBudget::PeriodGroup>   m_periods;
102 
103 };
104 
AccountGroup()105 MyMoneyBudget::AccountGroup::AccountGroup() :
106   d_ptr(new AccountGroupPrivate)
107 {
108 }
109 
AccountGroup(const MyMoneyBudget::AccountGroup & other)110 MyMoneyBudget::AccountGroup::AccountGroup(const MyMoneyBudget::AccountGroup& other) :
111   d_ptr(new AccountGroupPrivate(*other.d_func()))
112 {
113 }
114 
~AccountGroup()115 MyMoneyBudget::AccountGroup::~AccountGroup()
116 {
117   Q_D(AccountGroup);
118   delete d;
119 }
120 
isZero() const121 bool MyMoneyBudget::AccountGroup::isZero() const
122 {
123   Q_D(const AccountGroup);
124   return (!d->m_budgetsubaccounts && d->m_budgetlevel == eMyMoney::Budget::Level::Monthly && balance().isZero());
125 }
126 
convertToMonthly()127 void MyMoneyBudget::AccountGroup::convertToMonthly()
128 {
129   MyMoneyBudget::PeriodGroup period;
130 
131   Q_D(AccountGroup);
132   switch (d->m_budgetlevel) {
133     case eMyMoney::Budget::Level::Yearly:
134     case eMyMoney::Budget::Level::MonthByMonth:
135       period = d->m_periods.first();         // make him monthly
136       period.setAmount(balance() / MyMoneyMoney(12, 1));
137       clearPeriods();
138       addPeriod(period.startDate(), period);
139       break;
140     default:
141       break;
142   }
143   d->m_budgetlevel = eMyMoney::Budget::Level::Monthly;
144 }
145 
convertToYearly()146 void MyMoneyBudget::AccountGroup::convertToYearly()
147 {
148   MyMoneyBudget::PeriodGroup period;
149 
150   Q_D(AccountGroup);
151   switch (d->m_budgetlevel) {
152     case eMyMoney::Budget::Level::MonthByMonth:
153     case eMyMoney::Budget::Level::Monthly:
154       period = d->m_periods.first();         // make him monthly
155       period.setAmount(totalBalance());
156       clearPeriods();
157       addPeriod(period.startDate(), period);
158       break;
159     default:
160       break;
161   }
162   d->m_budgetlevel = eMyMoney::Budget::Level::Yearly;
163 }
164 
convertToMonthByMonth()165 void MyMoneyBudget::AccountGroup::convertToMonthByMonth()
166 {
167   MyMoneyBudget::PeriodGroup period;
168   QDate date;
169 
170   Q_D(AccountGroup);
171   switch (d->m_budgetlevel) {
172     case eMyMoney::Budget::Level::Yearly:
173     case eMyMoney::Budget::Level::Monthly:
174       period = d->m_periods.first();
175       period.setAmount(totalBalance() / MyMoneyMoney(12, 1));
176       clearPeriods();
177       date = period.startDate();
178       for (auto i = 0; i < 12; ++i) {
179         addPeriod(date, period);
180         date = date.addMonths(1);
181         period.setStartDate(date);
182       }
183       break;
184     default:
185       break;
186   }
187   d->m_budgetlevel = eMyMoney::Budget::Level::MonthByMonth;
188 }
189 
id() const190 QString MyMoneyBudget::AccountGroup::id() const
191 {
192   Q_D(const AccountGroup);
193   return d->m_id;
194 }
195 
setId(const QString & id)196 void MyMoneyBudget::AccountGroup::setId(const QString& id)
197 {
198   Q_D(AccountGroup);
199   d->m_id = id;
200 }
201 
budgetSubaccounts() const202 bool MyMoneyBudget::AccountGroup::budgetSubaccounts() const
203 {
204   Q_D(const AccountGroup);
205   return d->m_budgetsubaccounts;
206 }
207 
setBudgetSubaccounts(bool budgetsubaccounts)208 void MyMoneyBudget::AccountGroup::setBudgetSubaccounts(bool budgetsubaccounts)
209 {
210   Q_D(AccountGroup);
211   d->m_budgetsubaccounts = budgetsubaccounts;
212 }
213 
budgetLevel() const214 eMyMoney::Budget::Level MyMoneyBudget::AccountGroup::budgetLevel() const
215 {
216   Q_D(const AccountGroup);
217   return d->m_budgetlevel;
218 }
219 
setBudgetLevel(eMyMoney::Budget::Level level)220 void MyMoneyBudget::AccountGroup::setBudgetLevel(eMyMoney::Budget::Level level)
221 {
222   Q_D(AccountGroup);
223   d->m_budgetlevel = level;
224 }
225 
period(const QDate & date) const226 MyMoneyBudget::PeriodGroup MyMoneyBudget::AccountGroup::period(const QDate& date) const
227 {
228   Q_D(const AccountGroup);
229   return d->m_periods[date];
230 }
231 
addPeriod(const QDate & date,PeriodGroup & period)232 void MyMoneyBudget::AccountGroup::addPeriod(const QDate& date, PeriodGroup& period)
233 {
234   Q_D(AccountGroup);
235   d->m_periods[date] = period;
236 }
237 
getPeriods() const238 const QMap<QDate, MyMoneyBudget::PeriodGroup> MyMoneyBudget::AccountGroup::getPeriods() const
239 {
240   Q_D(const AccountGroup);
241   return d->m_periods;
242 }
243 
clearPeriods()244 void MyMoneyBudget::AccountGroup::clearPeriods()
245 {
246   Q_D(AccountGroup);
247   d->m_periods.clear();
248 }
249 
balance() const250 MyMoneyMoney MyMoneyBudget::AccountGroup::balance() const
251 {
252   Q_D(const AccountGroup);
253   MyMoneyMoney balance;
254 
255   foreach (const auto period, d->m_periods)
256     balance += period.amount();
257   return balance;
258 }
259 
totalBalance() const260 MyMoneyMoney MyMoneyBudget::AccountGroup::totalBalance() const
261 {
262   Q_D(const AccountGroup);
263   auto bal = balance();
264   switch (d->m_budgetlevel) {
265     default:
266       break;
267     case eMyMoney::Budget::Level::Monthly:
268       bal = bal * 12;
269       break;
270   }
271   return bal;
272 }
273 
operator +=(const MyMoneyBudget::AccountGroup & right)274 MyMoneyBudget::AccountGroup MyMoneyBudget::AccountGroup::operator += (const MyMoneyBudget::AccountGroup& right)
275 {
276   Q_D(AccountGroup);
277   auto d2 = static_cast<const AccountGroupPrivate *>(right.d_func());
278   // in case the right side is empty, we're done
279   if (d2->m_budgetlevel == eMyMoney::Budget::Level::None)
280     return *this;
281 
282   MyMoneyBudget::AccountGroup r(right);
283   auto d3 = static_cast<const AccountGroupPrivate *>(r.d_func());
284 
285   // make both operands based on the same budget level
286   if (d->m_budgetlevel != d3->m_budgetlevel) {
287     if (d->m_budgetlevel == eMyMoney::Budget::Level::Monthly) {        // my budget is monthly
288       if (d3->m_budgetlevel == eMyMoney::Budget::Level::Yearly) {     // his is yearly
289         r.convertToMonthly();
290       } else if (d3->m_budgetlevel == eMyMoney::Budget::Level::MonthByMonth) { // his is month by month
291         convertToMonthByMonth();
292       }
293     } else if (d->m_budgetlevel == eMyMoney::Budget::Level::Yearly) {  // my budget is yearly
294       if (d3->m_budgetlevel == eMyMoney::Budget::Level::Monthly) {    // his is monthly
295         r.convertToYearly();
296       } else if (d3->m_budgetlevel == eMyMoney::Budget::Level::MonthByMonth) { // his is month by month
297         convertToMonthByMonth();
298       }
299     } else if (d->m_budgetlevel == eMyMoney::Budget::Level::MonthByMonth) {  // my budget is month by month
300       r.convertToMonthByMonth();
301     }
302   }
303 
304   QMap<QDate, MyMoneyBudget::PeriodGroup> rPeriods = d3->m_periods;
305   QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_pr;
306 
307   // in case the left side is empty, we add empty periods
308   // so that both budgets are identical
309   if (d->m_budgetlevel == eMyMoney::Budget::Level::None) {
310     it_pr = rPeriods.constBegin();
311     QDate date = (*it_pr).startDate();
312     while (it_pr != rPeriods.constEnd()) {
313       MyMoneyBudget::PeriodGroup period = *it_pr;
314       period.setAmount(MyMoneyMoney());
315       addPeriod(date, period);
316       date = date.addMonths(1);
317       ++it_pr;
318     }
319     d->m_budgetlevel = d3->m_budgetlevel;
320   }
321 
322   QMap<QDate, MyMoneyBudget::PeriodGroup> periods = d->m_periods;
323   QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_p;
324 
325   // now both budgets should be of the same type and we simply need
326   // to iterate over the period list and add the values
327   d->m_periods.clear();
328   it_p = periods.constBegin();
329   it_pr = rPeriods.constBegin();
330   QDate date = (*it_p).startDate();
331   while (it_p != periods.constEnd()) {
332     MyMoneyBudget::PeriodGroup period = *it_p;
333     if (it_pr != rPeriods.constEnd()) {
334       period.setAmount(period.amount() + (*it_pr).amount());
335       ++it_pr;
336     }
337     addPeriod(date, period);
338     date = date.addMonths(1);
339     ++it_p;
340   }
341   return *this;
342 }
343 
operator ==(const AccountGroup & right) const344 bool MyMoneyBudget::AccountGroup::operator == (const AccountGroup& right) const
345 {
346   Q_D(const AccountGroup);
347   auto d2 = static_cast<const AccountGroupPrivate *>(right.d_func());
348   return (d->m_id == d2->m_id
349           && d->m_budgetlevel == d2->m_budgetlevel
350           && d->m_budgetsubaccounts == d2->m_budgetsubaccounts
351           && d->m_periods.keys() == d2->m_periods.keys()
352           && d->m_periods.values() == d2->m_periods.values());
353 }
354 
MyMoneyBudget()355 MyMoneyBudget::MyMoneyBudget() :
356   MyMoneyObject(*new MyMoneyBudgetPrivate)
357 {
358   Q_D(MyMoneyBudget);
359   d->m_name = QLatin1Literal("Unconfigured Budget");
360 }
361 
MyMoneyBudget(const QString & id)362 MyMoneyBudget::MyMoneyBudget(const QString &id) :
363   MyMoneyObject(*new MyMoneyBudgetPrivate, id)
364 {
365   Q_D(MyMoneyBudget);
366   d->m_name = QLatin1Literal("Unconfigured Budget");
367 }
368 
MyMoneyBudget(const QString & id,const MyMoneyBudget & other)369 MyMoneyBudget::MyMoneyBudget(const QString& id, const MyMoneyBudget& other) :
370   MyMoneyObject(*new MyMoneyBudgetPrivate(*other.d_func()), id)
371 {
372 }
373 
MyMoneyBudget(const MyMoneyBudget & other)374 MyMoneyBudget::MyMoneyBudget(const MyMoneyBudget& other) :
375   MyMoneyObject(*new MyMoneyBudgetPrivate(*other.d_func()), other.id())
376 {
377 }
378 
~MyMoneyBudget()379 MyMoneyBudget::~MyMoneyBudget()
380 {
381 }
382 
operator ==(const MyMoneyBudget & right) const383 bool MyMoneyBudget::operator == (const MyMoneyBudget& right) const
384 {
385   Q_D(const MyMoneyBudget);
386   auto d2 = static_cast<const MyMoneyBudgetPrivate *>(right.d_func());
387   return (MyMoneyObject::operator==(right) &&
388           (d->m_accounts.count() == d2->m_accounts.count()) &&
389           (d->m_accounts.keys() == d2->m_accounts.keys()) &&
390           (d->m_accounts.values() == d2->m_accounts.values()) &&
391           (d->m_name == d2->m_name) &&
392           (d->m_start == d2->m_start));
393 }
394 
hasReferenceTo(const QString & id) const395 bool MyMoneyBudget::hasReferenceTo(const QString& id) const
396 {
397   Q_D(const MyMoneyBudget);
398   // return true if we have an assignment for this id
399   return (d->m_accounts.contains(id));
400 }
401 
removeReference(const QString & id)402 void MyMoneyBudget::removeReference(const QString& id)
403 {
404   Q_D(MyMoneyBudget);
405   if (d->m_accounts.contains(id)) {
406     d->m_accounts.remove(id);
407   }
408 }
409 
account(const QString & id) const410 const MyMoneyBudget::AccountGroup& MyMoneyBudget::account(const QString& id) const
411 {
412   static AccountGroup empty;
413   QMap<QString, AccountGroup>::ConstIterator it;
414 
415   Q_D(const MyMoneyBudget);
416   it = d->m_accounts.constFind(id);
417   if (it != d->m_accounts.constEnd())
418     return it.value();
419   return empty;
420 }
421 
setAccount(const AccountGroup & account,const QString & id)422 void MyMoneyBudget::setAccount(const AccountGroup& account, const QString& id)
423 {
424   Q_D(MyMoneyBudget);
425   if (account.isZero()) {
426     d->m_accounts.remove(id);
427   } else {
428     // make sure we store a correct id
429     AccountGroup acc(account);
430     if (acc.id() != id)
431       acc.setId(id);
432     d->m_accounts[id] = acc;
433   }
434 }
435 
contains(const QString & id) const436 bool MyMoneyBudget::contains(const QString &id) const
437 {
438   Q_D(const MyMoneyBudget);
439   return d->m_accounts.contains(id);
440 }
441 
getaccounts() const442 QList<MyMoneyBudget::AccountGroup> MyMoneyBudget::getaccounts() const
443 {
444   Q_D(const MyMoneyBudget);
445   return d->m_accounts.values();
446 }
447 
accountsMap() const448 QMap<QString, MyMoneyBudget::AccountGroup> MyMoneyBudget::accountsMap() const
449 {
450   Q_D(const MyMoneyBudget);
451   return d->m_accounts;
452 }
453 
name() const454 QString MyMoneyBudget::name() const
455 {
456   Q_D(const MyMoneyBudget);
457   return d->m_name;
458 }
459 
setName(const QString & name)460 void MyMoneyBudget::setName(const QString& name)
461 {
462   Q_D(MyMoneyBudget);
463   d->m_name = name;
464 }
465 
budgetStart() const466 QDate MyMoneyBudget::budgetStart() const
467 {
468   Q_D(const MyMoneyBudget);
469   return d->m_start;
470 }
471 
setBudgetStart(const QDate & start)472 void MyMoneyBudget::setBudgetStart(const QDate& start)
473 {
474   Q_D(MyMoneyBudget);
475   auto oldDate = QDate(d->m_start.year(), d->m_start.month(), 1);
476   d->m_start = QDate(start.year(), start.month(), 1);
477   if (oldDate.isValid()) {
478     int adjust = ((d->m_start.year() - oldDate.year()) * 12) + (d->m_start.month() - oldDate.month());
479     QMap<QString, AccountGroup>::iterator it;
480     for (it = d->m_accounts.begin(); it != d->m_accounts.end(); ++it) {
481       const QMap<QDate, PeriodGroup> periods = (*it).getPeriods();
482       QMap<QDate, PeriodGroup>::const_iterator it_per;
483       (*it).clearPeriods();
484       for (it_per = periods.begin(); it_per != periods.end(); ++it_per) {
485         PeriodGroup pgroup = (*it_per);
486         pgroup.setStartDate(pgroup.startDate().addMonths(adjust));
487         (*it).addPeriod(pgroup.startDate(), pgroup);
488       }
489     }
490   }
491 }
492