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