File indexing completed on 2024-05-19 05:08:30

0001 /*
0002     SPDX-FileCopyrightText: 2006 Darren Gould <darren_gould@gmx.de>
0003     SPDX-FileCopyrightText: 2009-2014 Alvaro Soliverez <asoliverez@gmail.com>
0004     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0005     SPDX-FileCopyrightText: 2019 Thomas Baumgart <tbaumgart@kde.org>
0006     SPDX-FileCopyrightText: 2020 Robert Szczesiak <dev.rszczesiak@gmail.com>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "budgetviewproxymodel.h"
0011 
0012 // ----------------------------------------------------------------------------
0013 // QT Includes
0014 
0015 #include <QColor>
0016 
0017 // ----------------------------------------------------------------------------
0018 // KDE Includes
0019 
0020 // ----------------------------------------------------------------------------
0021 // Project Includes
0022 
0023 #include "mymoneyutils.h"
0024 #include "mymoneyfile.h"
0025 #include "mymoneyaccount.h"
0026 #include "mymoneysecurity.h"
0027 #include "mymoneymoney.h"
0028 #include "mymoneybudget.h"
0029 #include "accountsproxymodel.h"
0030 #include "accountsproxymodel_p.h"
0031 #include "budgetviewproxymodel.h"
0032 #include "mymoneyenums.h"
0033 #include "mymoneymodelbase.h"
0034 
0035 
0036 class BudgetViewProxyModelPrivate : public AccountsProxyModelPrivate
0037 {
0038     Q_DISABLE_COPY(BudgetViewProxyModelPrivate)
0039 
0040 public:
0041     BudgetViewProxyModelPrivate() :
0042         AccountsProxyModelPrivate()
0043     {
0044     }
0045 
0046     ~BudgetViewProxyModelPrivate() override
0047     {
0048     }
0049 
0050     QString accountName(const QModelIndex& idx, const QString& basename) const
0051     {
0052         // only return a modified name for asset or liability accounts
0053         if (idx.data(eMyMoney::Model::AccountIsAssetLiabilityRole).toBool()) {
0054             const auto accountId = idx.data(eMyMoney::Model::IdRole).toString();
0055             eMyMoney::Account::Type budgetType;
0056             // in case we have an entry for that account in the budget
0057             // we use that type, if not, we add the default found in
0058             // with the account. If the default is not to include the
0059             // account in the budget, we should never come around here
0060             // because BudgetViewProxyModel::filterAcceptsRow() should
0061             // filter out the entry
0062             if (m_budget.account(accountId).id() == accountId) {
0063                 budgetType = m_budget.account(accountId).budgetType();
0064             } else {
0065                 budgetType = idx.data(eMyMoney::Model::AccountTypeInBudgetRole).value<eMyMoney::Account::Type>();
0066             }
0067             // only append the type if it is income or expense
0068             if (MyMoneyAccount::isIncomeExpense(budgetType)) {
0069                 return QStringLiteral("%1 (%2)").arg(basename, MyMoneyAccount::accountTypeToString(budgetType));
0070             }
0071         }
0072         return basename;
0073     }
0074 
0075     MyMoneyBudget   m_budget;
0076     MyMoneyMoney    m_lastBalance;
0077     QColor          positiveScheme;
0078     QColor          negativeScheme;
0079 };
0080 
0081 BudgetViewProxyModel::BudgetViewProxyModel(QObject *parent) :
0082     AccountsProxyModel(*new BudgetViewProxyModelPrivate, parent)
0083 {
0084     setFilterCaseSensitivity(Qt::CaseInsensitive);
0085 }
0086 
0087 BudgetViewProxyModel::~BudgetViewProxyModel()
0088 {
0089 }
0090 
0091 void BudgetViewProxyModel::setColorScheme(AccountsModel::ColorScheme scheme, const QColor& color)
0092 {
0093     Q_D(BudgetViewProxyModel);
0094     switch(scheme) {
0095     case AccountsModel::Positive:
0096         d->positiveScheme = color;
0097         break;
0098     case AccountsModel::Negative:
0099         d->negativeScheme = color;
0100         break;
0101     }
0102 }
0103 
0104 
0105 /**
0106   * This function was reimplemented to add the data needed by the other columns that this model
0107   * is adding besides the columns of the @ref AccountsModel.
0108   */
0109 QVariant BudgetViewProxyModel::data(const QModelIndex & idx, int role) const
0110 {
0111     Q_D(const BudgetViewProxyModel);
0112 
0113 #if 0
0114     static QVector<Column> columnsToProcess {Column::TotalBalance, Column::TotalValue/*, AccountsModel::PostedValue, Column::Account*/};
0115     if (columnsToProcess.contains(sourceColumn)) {
0116 #endif
0117 
0118     // get index in base model
0119     const auto accountIdx = MyMoneyModelBase::mapToBaseSource(idx);
0120 
0121     switch (role) {
0122     case Qt::DisplayRole: {
0123         const auto file = MyMoneyFile::instance();
0124         const auto accountId = accountIdx.data(eMyMoney::Model::IdRole).toString();
0125         const auto currencyId = accountIdx.data(eMyMoney::Model::AccountCurrencyIdRole).toString();
0126         const auto baseCurrency = file->baseCurrency();
0127 
0128         switch (idx.column()) {
0129         case AccountsModel::Column::AccountName:
0130             return d->accountName(idx, AccountsProxyModel::data(idx, role).toString());
0131 
0132         case AccountsModel::Column::Balance:
0133             if (currencyId != baseCurrency.id())
0134                 return MyMoneyUtils::formatMoney(accountBalance(accountId).abs(), file->security(currencyId));
0135             else
0136                 return QVariant();
0137         case AccountsModel::Column::TotalPostedValue:
0138             return MyMoneyUtils::formatMoney(computeTotalValue(accountIdx).abs(), baseCurrency);
0139         // FIXME: Posted value doesn't correspond with total value without below code. Investigate why and whether it matters.
0140         //              case AccountsModel::PostedValue:
0141         //                return QVariant(MyMoneyUtils::formatMoney(accountValue(account, accountBalance(account.id())), file->baseCurrency()));
0142         default:
0143             break;
0144         }
0145     } break;
0146 
0147     case eMyMoney::Model::AccountTotalValueRole:
0148         return QVariant::fromValue(computeTotalValue(accountIdx));
0149 
0150     case Qt::ForegroundRole:
0151         // show all numbers in positive scheme
0152         switch (idx.column()) {
0153         case AccountsModel::Column::Balance:
0154         case AccountsModel::Column::PostedValue:
0155         case AccountsModel::Column::TotalPostedValue:
0156             return d->positiveScheme;
0157 
0158         default:
0159             break;
0160         }
0161         break;
0162 
0163 #if 0
0164     case (int)Role::Balance:
0165         if (account.currencyId() != file->baseCurrency().id())
0166             return QVariant::fromValue(accountBalance(account.id()));
0167         else
0168             return QVariant();
0169     case (int)Role::Value:
0170         return QVariant::fromValue(accountValue(account, accountBalance(account.id())));
0171 #endif
0172     default:
0173         break;
0174     }
0175     return AccountsProxyModel::data(idx, role);
0176 }
0177 
0178 Qt::ItemFlags BudgetViewProxyModel::flags(const QModelIndex& index) const
0179 {
0180     Q_D(const BudgetViewProxyModel);
0181     Qt::ItemFlags flags = AccountsProxyModel::flags(index);
0182     if (!index.parent().isValid())
0183         return flags & ~Qt::ItemIsSelectable;
0184 
0185     // check if any of the parent accounts has the 'include subaccounts'
0186     // flag set. If so, we don't allow selecting this account
0187     QModelIndex idx = index.parent();
0188     while (idx.isValid()) {
0189         const auto accountIdx = MyMoneyFile::baseModel()->mapToBaseSource(index);
0190         const auto accountId = accountIdx.data(eMyMoney::Model::IdRole).toString();
0191         if (!accountId.isEmpty()) {
0192             // find out if the account is budgeted
0193             MyMoneyBudget::AccountGroup budgetAccount = d->m_budget.account(accountId);
0194             if (budgetAccount.id() == accountId) {
0195                 if (budgetAccount.budgetSubaccounts()) {
0196                     return flags & ~Qt::ItemIsEnabled;
0197                 }
0198             }
0199         }
0200         idx = idx.parent();
0201     }
0202     return flags;
0203 }
0204 
0205 void BudgetViewProxyModel::setBudget(const MyMoneyBudget& budget)
0206 {
0207     Q_D(BudgetViewProxyModel);
0208     d->m_budget = budget;
0209     invalidate();
0210     checkBalance();
0211 }
0212 
0213 bool BudgetViewProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
0214 {
0215     Q_D(const BudgetViewProxyModel);
0216     // we want to include
0217     // a) all income and expense accounts
0218     // b) all asset and liability accounts that are marked to be included in budget
0219     // c) all asset and liability accounts that are contained in budget
0220     const auto idx = sourceModel()->index(source_row, 0, source_parent);
0221     auto accountType = idx.data(eMyMoney::Model::AccountTypeInBudgetRole).value<eMyMoney::Account::Type>();
0222     // accountType is either income or expense for cases a) and b)
0223 
0224     // find out if the account is budgeted
0225     const auto accountId = idx.data(eMyMoney::Model::IdRole).toString();
0226     const auto budgetAccount = d->m_budget.account(accountId);
0227 
0228     if (!idx.data(eMyMoney::Model::AccountIsIncomeExpenseRole).toBool()) {
0229         if (budgetAccount.id() == accountId) {
0230             // case c)
0231             accountType = budgetAccount.budgetType();
0232         }
0233     }
0234 
0235     if (MyMoneyAccount::isIncomeExpense(accountType)) {
0236         if (hideUnusedIncomeExpenseAccounts()) {
0237             MyMoneyMoney balance;
0238             if (budgetAccount.id() == accountId) {
0239                 balance = budgetAccount.balance();
0240                 switch (budgetAccount.budgetLevel()) {
0241                 case eMyMoney::Budget::Level::Monthly:
0242                     balance *= MyMoneyMoney(12);
0243                     break;
0244                 default:
0245                     break;
0246                 }
0247             }
0248             if (!balance.isZero())
0249                 return AccountsProxyModel::filterAcceptsRow(source_row, source_parent);
0250 
0251             for (auto i = 0; i < sourceModel()->rowCount(idx); ++i) {
0252                 if (filterAcceptsRow(i, idx))
0253                     return true;
0254             }
0255             return false;
0256         }
0257         return AccountsProxyModel::filterAcceptsRow(source_row, source_parent);
0258     }
0259     return false;
0260 }
0261 
0262 MyMoneyMoney BudgetViewProxyModel::accountBalance(const QString& accountId) const
0263 {
0264     Q_D(const BudgetViewProxyModel);
0265     MyMoneyMoney balance;
0266     // find out if the account is budgeted
0267     MyMoneyBudget::AccountGroup budgetAccount = d->m_budget.account(accountId);
0268     if (budgetAccount.id() == accountId) {
0269         balance = budgetAccount.balance();
0270         switch (budgetAccount.budgetLevel()) {
0271         case eMyMoney::Budget::Level::Monthly:
0272             balance *= MyMoneyMoney(12);
0273             break;
0274         default:
0275             break;
0276         }
0277     }
0278     return balance;
0279 }
0280 
0281 MyMoneyMoney BudgetViewProxyModel::accountBalance(const QModelIndex& idx) const
0282 {
0283     Q_D(const BudgetViewProxyModel);
0284     MyMoneyMoney balance;
0285     const auto accountId = idx.data(eMyMoney::Model::IdRole).toString();
0286     // find out if the account is budgeted
0287     MyMoneyBudget::AccountGroup budgetAccount = d->m_budget.account(accountId);
0288     if (budgetAccount.id() == accountId) {
0289         balance = budgetAccount.balance();
0290         switch (budgetAccount.budgetLevel()) {
0291         case eMyMoney::Budget::Level::Monthly:
0292             balance *= MyMoneyMoney(12);
0293             break;
0294         default:
0295             break;
0296         }
0297         // revert sign if it is an expense account or is treated as an expense
0298         if ((idx.data(eMyMoney::Model::AccountTypeRole).value<eMyMoney::Account::Type>() == eMyMoney::Account::Type::Expense)
0299             || (budgetAccount.budgetType() == eMyMoney::Account::Type::Expense)) {
0300             balance = -balance;
0301         }
0302     }
0303     return balance;
0304 }
0305 
0306 MyMoneyMoney BudgetViewProxyModel::computeTotalValue(const QModelIndex& source_index) const
0307 {
0308     // get balance of this account
0309     const auto accountId = source_index.data(eMyMoney::Model::IdRole).toString();
0310     auto totalValue = MyMoneyFile::instance()->accountsModel()->balanceToValue(accountId, accountBalance(source_index)).first;
0311 
0312     // and all children
0313     auto model = sourceModel();
0314     const auto rows = model->rowCount(source_index);
0315     for (auto i = 0; i < rows; ++i)
0316         totalValue += computeTotalValue(model->index(i, 0, source_index));
0317     return totalValue;
0318 }
0319 
0320 void BudgetViewProxyModel::checkBalance()
0321 {
0322     Q_D(BudgetViewProxyModel);
0323     const auto file = MyMoneyFile::instance();
0324 
0325     const auto incomeIdx = MyMoneyModelBase::mapFromBaseSource(this, file->accountsModel()->incomeIndex());
0326     const auto expenseIdx = MyMoneyModelBase::mapFromBaseSource(this, file->accountsModel()->expenseIndex());
0327     const auto assetIdx = MyMoneyModelBase::mapFromBaseSource(this, file->accountsModel()->assetIndex());
0328     const auto liabilityIdx = MyMoneyModelBase::mapFromBaseSource(this, file->accountsModel()->liabilityIndex());
0329 
0330     const MyMoneyMoney balance = incomeIdx.data(eMyMoney::Model::AccountTotalValueRole).value<MyMoneyMoney>()
0331         + expenseIdx.data(eMyMoney::Model::AccountTotalValueRole).value<MyMoneyMoney>()
0332         + assetIdx.data(eMyMoney::Model::AccountTotalValueRole).value<MyMoneyMoney>()
0333         + liabilityIdx.data(eMyMoney::Model::AccountTotalValueRole).value<MyMoneyMoney>();
0334 
0335     if (d->m_lastBalance != balance) {
0336         d->m_lastBalance = balance;
0337         Q_EMIT balanceChanged(d->m_lastBalance);
0338     }
0339 }