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 }