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 }