File indexing completed on 2024-05-19 05:07:15
0001 /* 0002 SPDX-FileCopyrightText: 2006 Ace Jones <acejones@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2006 Darren Gould <darren_gould@gmx.de> 0004 SPDX-FileCopyrightText: 2010-2019 Thomas Baumgart <tbaumgart@kde.org> 0005 SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "mymoneybudget.h" 0010 #include "mymoneybudget_p.h" 0011 0012 // ---------------------------------------------------------------------------- 0013 // QT Includes 0014 0015 #include <QMap> 0016 #include <QSet> 0017 0018 // ---------------------------------------------------------------------------- 0019 // KDE Includes 0020 0021 // ---------------------------------------------------------------------------- 0022 // Project Includes 0023 0024 class MyMoneyBudget::PeriodGroupPrivate 0025 { 0026 public: 0027 QDate m_start; 0028 MyMoneyMoney m_amount; 0029 }; 0030 0031 MyMoneyBudget::PeriodGroup::PeriodGroup() : 0032 d_ptr(new PeriodGroupPrivate) 0033 { 0034 } 0035 0036 MyMoneyBudget::PeriodGroup::PeriodGroup(const MyMoneyBudget::PeriodGroup & other) : 0037 d_ptr(new PeriodGroupPrivate(*other.d_func())) 0038 { 0039 } 0040 0041 MyMoneyBudget::PeriodGroup::~PeriodGroup() 0042 { 0043 Q_D(PeriodGroup); 0044 delete d; 0045 } 0046 0047 QDate MyMoneyBudget::PeriodGroup::startDate() const 0048 { 0049 Q_D(const PeriodGroup); 0050 return d->m_start; 0051 } 0052 0053 void MyMoneyBudget::PeriodGroup::setStartDate(const QDate& start) 0054 { 0055 Q_D(PeriodGroup); 0056 d->m_start = start; 0057 } 0058 0059 MyMoneyMoney MyMoneyBudget::PeriodGroup::amount() const 0060 { 0061 Q_D(const PeriodGroup); 0062 return d->m_amount; 0063 } 0064 0065 void MyMoneyBudget::PeriodGroup::setAmount(const MyMoneyMoney& amount) 0066 { 0067 Q_D(PeriodGroup); 0068 d->m_amount = amount; 0069 } 0070 0071 bool MyMoneyBudget::PeriodGroup::operator == (const PeriodGroup& right) const 0072 { 0073 Q_D(const PeriodGroup); 0074 auto d2 = static_cast<const PeriodGroupPrivate *>(right.d_func()); 0075 return (d->m_start == d2->m_start && d->m_amount == d2->m_amount); 0076 } 0077 0078 class MyMoneyBudget::AccountGroupPrivate { 0079 0080 public: 0081 AccountGroupPrivate() 0082 : m_budgetlevel(eMyMoney::Budget::Level::None) 0083 , m_budgetType(eMyMoney::Account::Type::Unknown) 0084 , m_budgetsubaccounts(false) 0085 { 0086 } 0087 0088 QString m_id; 0089 eMyMoney::Budget::Level m_budgetlevel; 0090 eMyMoney::Account::Type m_budgetType; 0091 bool m_budgetsubaccounts; 0092 QMap<QDate, MyMoneyBudget::PeriodGroup> m_periods; 0093 0094 }; 0095 0096 MyMoneyBudget::AccountGroup::AccountGroup() : 0097 d_ptr(new AccountGroupPrivate) 0098 { 0099 } 0100 0101 MyMoneyBudget::AccountGroup::AccountGroup(const MyMoneyBudget::AccountGroup& other) : 0102 d_ptr(new AccountGroupPrivate(*other.d_func())) 0103 { 0104 } 0105 0106 MyMoneyBudget::AccountGroup::~AccountGroup() 0107 { 0108 Q_D(AccountGroup); 0109 delete d; 0110 } 0111 0112 bool MyMoneyBudget::AccountGroup::isZero() const 0113 { 0114 Q_D(const AccountGroup); 0115 return (!d->m_budgetsubaccounts && d->m_budgetlevel == eMyMoney::Budget::Level::Monthly && balance().isZero()); 0116 } 0117 0118 void MyMoneyBudget::AccountGroup::convertToMonthly() 0119 { 0120 MyMoneyBudget::PeriodGroup period; 0121 0122 Q_D(AccountGroup); 0123 switch (d->m_budgetlevel) { 0124 case eMyMoney::Budget::Level::Yearly: 0125 case eMyMoney::Budget::Level::MonthByMonth: 0126 period = d->m_periods.first(); // make him monthly 0127 period.setAmount(balance() / MyMoneyMoney(12, 1)); 0128 clearPeriods(); 0129 addPeriod(period.startDate(), period); 0130 break; 0131 default: 0132 break; 0133 } 0134 d->m_budgetlevel = eMyMoney::Budget::Level::Monthly; 0135 } 0136 0137 void MyMoneyBudget::AccountGroup::convertToYearly() 0138 { 0139 MyMoneyBudget::PeriodGroup period; 0140 0141 Q_D(AccountGroup); 0142 switch (d->m_budgetlevel) { 0143 case eMyMoney::Budget::Level::MonthByMonth: 0144 case eMyMoney::Budget::Level::Monthly: 0145 period = d->m_periods.first(); // make him monthly 0146 period.setAmount(totalBalance()); 0147 clearPeriods(); 0148 addPeriod(period.startDate(), period); 0149 break; 0150 default: 0151 break; 0152 } 0153 d->m_budgetlevel = eMyMoney::Budget::Level::Yearly; 0154 } 0155 0156 void MyMoneyBudget::AccountGroup::convertToMonthByMonth() 0157 { 0158 MyMoneyBudget::PeriodGroup period; 0159 QDate date; 0160 0161 Q_D(AccountGroup); 0162 switch (d->m_budgetlevel) { 0163 case eMyMoney::Budget::Level::Yearly: 0164 case eMyMoney::Budget::Level::Monthly: 0165 period = d->m_periods.first(); 0166 period.setAmount(totalBalance() / MyMoneyMoney(12, 1)); 0167 clearPeriods(); 0168 date = period.startDate(); 0169 for (auto i = 0; i < 12; ++i) { 0170 addPeriod(date, period); 0171 date = date.addMonths(1); 0172 period.setStartDate(date); 0173 } 0174 break; 0175 default: 0176 break; 0177 } 0178 d->m_budgetlevel = eMyMoney::Budget::Level::MonthByMonth; 0179 } 0180 0181 QString MyMoneyBudget::AccountGroup::id() const 0182 { 0183 Q_D(const AccountGroup); 0184 return d->m_id; 0185 } 0186 0187 void MyMoneyBudget::AccountGroup::setId(const QString& id) 0188 { 0189 Q_D(AccountGroup); 0190 d->m_id = id; 0191 } 0192 0193 bool MyMoneyBudget::AccountGroup::budgetSubaccounts() const 0194 { 0195 Q_D(const AccountGroup); 0196 return d->m_budgetsubaccounts; 0197 } 0198 0199 void MyMoneyBudget::AccountGroup::setBudgetSubaccounts(bool budgetsubaccounts) 0200 { 0201 Q_D(AccountGroup); 0202 d->m_budgetsubaccounts = budgetsubaccounts; 0203 } 0204 0205 eMyMoney::Budget::Level MyMoneyBudget::AccountGroup::budgetLevel() const 0206 { 0207 Q_D(const AccountGroup); 0208 return d->m_budgetlevel; 0209 } 0210 0211 void MyMoneyBudget::AccountGroup::setBudgetLevel(eMyMoney::Budget::Level level) 0212 { 0213 Q_D(AccountGroup); 0214 d->m_budgetlevel = level; 0215 } 0216 0217 eMyMoney::Account::Type MyMoneyBudget::AccountGroup::budgetType() const 0218 { 0219 Q_D(const AccountGroup); 0220 return d->m_budgetType; 0221 } 0222 0223 void MyMoneyBudget::AccountGroup::setBudgetType(eMyMoney::Account::Type type) 0224 { 0225 Q_D(AccountGroup); 0226 d->m_budgetType = type; 0227 } 0228 0229 MyMoneyBudget::PeriodGroup MyMoneyBudget::AccountGroup::period(const QDate& date) const 0230 { 0231 Q_D(const AccountGroup); 0232 return d->m_periods[date]; 0233 } 0234 0235 void MyMoneyBudget::AccountGroup::addPeriod(const QDate& date, PeriodGroup& period) 0236 { 0237 Q_D(AccountGroup); 0238 d->m_periods[date] = period; 0239 } 0240 0241 const QMap<QDate, MyMoneyBudget::PeriodGroup> MyMoneyBudget::AccountGroup::getPeriods() const 0242 { 0243 Q_D(const AccountGroup); 0244 return d->m_periods; 0245 } 0246 0247 void MyMoneyBudget::AccountGroup::clearPeriods() 0248 { 0249 Q_D(AccountGroup); 0250 d->m_periods.clear(); 0251 } 0252 0253 MyMoneyMoney MyMoneyBudget::AccountGroup::balance() const 0254 { 0255 Q_D(const AccountGroup); 0256 MyMoneyMoney balance; 0257 0258 for (const auto& period : d->m_periods) 0259 balance += period.amount(); 0260 return balance; 0261 } 0262 0263 MyMoneyMoney MyMoneyBudget::AccountGroup::totalBalance() const 0264 { 0265 Q_D(const AccountGroup); 0266 auto bal = balance(); 0267 switch (d->m_budgetlevel) { 0268 default: 0269 break; 0270 case eMyMoney::Budget::Level::Monthly: 0271 bal = bal * 12; 0272 break; 0273 } 0274 return bal; 0275 } 0276 0277 MyMoneyBudget::AccountGroup MyMoneyBudget::AccountGroup::operator += (const MyMoneyBudget::AccountGroup& right) 0278 { 0279 Q_D(AccountGroup); 0280 auto d2 = static_cast<const AccountGroupPrivate *>(right.d_func()); 0281 // in case the right side is empty, we're done 0282 if (d2->m_budgetlevel == eMyMoney::Budget::Level::None) 0283 return *this; 0284 0285 MyMoneyBudget::AccountGroup r(right); 0286 auto d3 = static_cast<const AccountGroupPrivate *>(r.d_func()); 0287 0288 // make both operands based on the same budget level 0289 if (d->m_budgetlevel != d3->m_budgetlevel) { 0290 if (d->m_budgetlevel == eMyMoney::Budget::Level::Monthly) { // my budget is monthly 0291 if (d3->m_budgetlevel == eMyMoney::Budget::Level::Yearly) { // his is yearly 0292 r.convertToMonthly(); 0293 } else if (d3->m_budgetlevel == eMyMoney::Budget::Level::MonthByMonth) { // his is month by month 0294 convertToMonthByMonth(); 0295 } 0296 } else if (d->m_budgetlevel == eMyMoney::Budget::Level::Yearly) { // my budget is yearly 0297 if (d3->m_budgetlevel == eMyMoney::Budget::Level::Monthly) { // his is monthly 0298 r.convertToYearly(); 0299 } else if (d3->m_budgetlevel == eMyMoney::Budget::Level::MonthByMonth) { // his is month by month 0300 convertToMonthByMonth(); 0301 } 0302 } else if (d->m_budgetlevel == eMyMoney::Budget::Level::MonthByMonth) { // my budget is month by month 0303 r.convertToMonthByMonth(); 0304 } 0305 } 0306 0307 QMap<QDate, MyMoneyBudget::PeriodGroup> rPeriods = d3->m_periods; 0308 QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_pr; 0309 0310 // in case the left side is empty, we add empty periods 0311 // so that both budgets are identical 0312 if (d->m_budgetlevel == eMyMoney::Budget::Level::None) { 0313 it_pr = rPeriods.constBegin(); 0314 QDate date = (*it_pr).startDate(); 0315 while (it_pr != rPeriods.constEnd()) { 0316 MyMoneyBudget::PeriodGroup period = *it_pr; 0317 period.setAmount(MyMoneyMoney()); 0318 addPeriod(date, period); 0319 date = date.addMonths(1); 0320 ++it_pr; 0321 } 0322 d->m_budgetlevel = d3->m_budgetlevel; 0323 } 0324 0325 QMap<QDate, MyMoneyBudget::PeriodGroup> periods = d->m_periods; 0326 QMap<QDate, MyMoneyBudget::PeriodGroup>::const_iterator it_p; 0327 0328 // now both budgets should be of the same type and we simply need 0329 // to iterate over the period list and add the values 0330 d->m_periods.clear(); 0331 it_p = periods.constBegin(); 0332 it_pr = rPeriods.constBegin(); 0333 QDate date = (*it_p).startDate(); 0334 while (it_p != periods.constEnd()) { 0335 MyMoneyBudget::PeriodGroup period = *it_p; 0336 if (it_pr != rPeriods.constEnd()) { 0337 period.setAmount(period.amount() + (*it_pr).amount()); 0338 ++it_pr; 0339 } 0340 addPeriod(date, period); 0341 date = date.addMonths(1); 0342 ++it_p; 0343 } 0344 return *this; 0345 } 0346 0347 bool MyMoneyBudget::AccountGroup::operator == (const AccountGroup& right) const 0348 { 0349 Q_D(const AccountGroup); 0350 auto d2 = static_cast<const AccountGroupPrivate *>(right.d_func()); 0351 return (d->m_id == d2->m_id // 0352 && d->m_budgetlevel == d2->m_budgetlevel // 0353 && d->m_budgetsubaccounts == d2->m_budgetsubaccounts // 0354 && d->m_periods == d2->m_periods); 0355 } 0356 0357 MyMoneyBudget::MyMoneyBudget() : 0358 MyMoneyObject(*new MyMoneyBudgetPrivate) 0359 { 0360 Q_D(MyMoneyBudget); 0361 d->m_name = QStringLiteral("Unconfigured Budget"); 0362 } 0363 0364 MyMoneyBudget::MyMoneyBudget(const QString &id) : 0365 MyMoneyObject(*new MyMoneyBudgetPrivate, id) 0366 { 0367 Q_D(MyMoneyBudget); 0368 d->m_name = QStringLiteral("Unconfigured Budget"); 0369 } 0370 0371 MyMoneyBudget::MyMoneyBudget(const QString& id, const MyMoneyBudget& other) : 0372 MyMoneyObject(*new MyMoneyBudgetPrivate(*other.d_func()), id) 0373 { 0374 } 0375 0376 MyMoneyBudget::MyMoneyBudget(const MyMoneyBudget& other) : 0377 MyMoneyObject(*new MyMoneyBudgetPrivate(*other.d_func()), other.id()) 0378 { 0379 } 0380 0381 MyMoneyBudget::~MyMoneyBudget() 0382 { 0383 } 0384 0385 bool MyMoneyBudget::operator == (const MyMoneyBudget& right) const 0386 { 0387 Q_D(const MyMoneyBudget); 0388 auto d2 = static_cast<const MyMoneyBudgetPrivate *>(right.d_func()); 0389 // clang-format off 0390 return (MyMoneyObject::operator==(right) 0391 && (d->m_accounts.count() == d2->m_accounts.count()) 0392 && (d->m_accounts == d2->m_accounts) 0393 && (d->m_name == d2->m_name) 0394 && (d->m_start == d2->m_start)); 0395 // clang-format on 0396 } 0397 0398 void MyMoneyBudget::removeReference(const QString& id) 0399 { 0400 Q_D(MyMoneyBudget); 0401 if (d->m_accounts.contains(id)) { 0402 d->m_accounts.remove(id); 0403 } 0404 d->clearReferences(); 0405 } 0406 0407 const MyMoneyBudget::AccountGroup& MyMoneyBudget::account(const QString& id) const 0408 { 0409 static AccountGroup empty; 0410 QMap<QString, AccountGroup>::ConstIterator it; 0411 0412 Q_D(const MyMoneyBudget); 0413 it = d->m_accounts.constFind(id); 0414 if (it != d->m_accounts.constEnd()) 0415 return it.value(); 0416 return empty; 0417 } 0418 0419 void MyMoneyBudget::setAccount(const AccountGroup& account, const QString& id) 0420 { 0421 Q_D(MyMoneyBudget); 0422 if (account.isZero()) { 0423 d->m_accounts.remove(id); 0424 } else { 0425 // make sure we store a correct id 0426 AccountGroup acc(account); 0427 if (acc.id() != id) 0428 acc.setId(id); 0429 d->m_accounts[id] = acc; 0430 } 0431 d->clearReferences(); 0432 } 0433 0434 bool MyMoneyBudget::contains(const QString &id) const 0435 { 0436 Q_D(const MyMoneyBudget); 0437 return d->m_accounts.contains(id); 0438 } 0439 0440 QList<MyMoneyBudget::AccountGroup> MyMoneyBudget::getaccounts() const 0441 { 0442 Q_D(const MyMoneyBudget); 0443 return d->m_accounts.values(); 0444 } 0445 0446 QMap<QString, MyMoneyBudget::AccountGroup> MyMoneyBudget::accountsMap() const 0447 { 0448 Q_D(const MyMoneyBudget); 0449 return d->m_accounts; 0450 } 0451 0452 QString MyMoneyBudget::name() const 0453 { 0454 Q_D(const MyMoneyBudget); 0455 return d->m_name; 0456 } 0457 0458 void MyMoneyBudget::setName(const QString& name) 0459 { 0460 Q_D(MyMoneyBudget); 0461 d->m_name = name; 0462 } 0463 0464 QDate MyMoneyBudget::budgetStart() const 0465 { 0466 Q_D(const MyMoneyBudget); 0467 return d->m_start; 0468 } 0469 0470 void MyMoneyBudget::setBudgetStart(const QDate& start) 0471 { 0472 Q_D(MyMoneyBudget); 0473 auto oldDate = QDate(d->m_start.year(), d->m_start.month(), 1); 0474 d->m_start = QDate(start.year(), start.month(), 1); 0475 if (oldDate.isValid()) { 0476 int adjust = ((d->m_start.year() - oldDate.year()) * 12) + (d->m_start.month() - oldDate.month()); 0477 QMap<QString, AccountGroup>::iterator it; 0478 for (it = d->m_accounts.begin(); it != d->m_accounts.end(); ++it) { 0479 const QMap<QDate, PeriodGroup> periods = (*it).getPeriods(); 0480 QMap<QDate, PeriodGroup>::const_iterator it_per; 0481 (*it).clearPeriods(); 0482 for (it_per = periods.begin(); it_per != periods.end(); ++it_per) { 0483 PeriodGroup pgroup = (*it_per); 0484 pgroup.setStartDate(pgroup.startDate().addMonths(adjust)); 0485 (*it).addPeriod(pgroup.startDate(), pgroup); 0486 } 0487 } 0488 } 0489 }