File indexing completed on 2024-05-12 16:42:17
0001 /* 0002 SPDX-FileCopyrightText: 2010-2014 Cristian Oneț <onet.cristian@gmail.com> 0003 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-FileCopyrightText: 2020 Robert Szczesiak <dev.rszczesiak@gmail.com> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "accountsmodel.h" 0009 0010 // ---------------------------------------------------------------------------- 0011 // QT Includes 0012 0013 #include <QIcon> 0014 0015 // ---------------------------------------------------------------------------- 0016 // KDE Includes 0017 0018 #include <KLocalizedString> 0019 0020 // ---------------------------------------------------------------------------- 0021 // Project Includes 0022 0023 #include "mymoneyutils.h" 0024 #include "mymoneymoney.h" 0025 #include "mymoneyexception.h" 0026 #include "mymoneyfile.h" 0027 #include "mymoneyinstitution.h" 0028 #include "mymoneyaccount.h" 0029 #include "mymoneysecurity.h" 0030 #include "mymoneyprice.h" 0031 #include "kmymoneysettings.h" 0032 #include "icons.h" 0033 #include "modelenums.h" 0034 #include "mymoneyenums.h" 0035 #include "viewenums.h" 0036 0037 using namespace Icons; 0038 using namespace eAccountsModel; 0039 using namespace eMyMoney; 0040 0041 class AccountsModelPrivate 0042 { 0043 Q_DECLARE_PUBLIC(AccountsModel) 0044 0045 public: 0046 /** 0047 * The pimpl. 0048 */ 0049 AccountsModelPrivate(AccountsModel *qq) : 0050 q_ptr(qq), 0051 m_file(MyMoneyFile::instance()) 0052 { 0053 m_columns.append(Column::Account); 0054 } 0055 0056 virtual ~AccountsModelPrivate() 0057 { 0058 } 0059 0060 void init() 0061 { 0062 Q_Q(AccountsModel); 0063 QStringList headerLabels; 0064 for (const auto& column : qAsConst(m_columns)) 0065 headerLabels.append(q->getHeaderName(column)); 0066 q->setHorizontalHeaderLabels(headerLabels); 0067 } 0068 0069 void loadPreferredAccount(const MyMoneyAccount &acc, QStandardItem *fromNode /*accounts' regular node*/, const int row, QStandardItem *toNode /*accounts' favourite node*/) 0070 { 0071 if (acc.value(QStringLiteral("PreferredAccount")) != QLatin1String("Yes")) 0072 return; 0073 0074 auto favRow = toNode->rowCount(); 0075 if (auto favItem = itemFromAccountId(toNode, acc.id())) { 0076 favRow = favItem->row(); 0077 toNode->removeRow(favRow); 0078 } 0079 0080 auto itemToClone = fromNode->child(row); 0081 if (itemToClone) 0082 toNode->insertRow(favRow, itemToClone->clone()); 0083 } 0084 0085 /** 0086 * Load all the sub-accounts recursively. 0087 * 0088 * @param model The model in which to load the data. 0089 * @param accountsItem The item from the model of the parent account of the sub-accounts which are being loaded. 0090 * @param favoriteAccountsItem The item of the favorites accounts groups so favorite accounts can be added here also. 0091 * @param list The list of the account id's of the sub-accounts which are being loaded. 0092 * 0093 */ 0094 void loadSubaccounts(QStandardItem *node, QStandardItem *favoriteAccountsItem, const QStringList& subaccounts) 0095 { 0096 for (const auto& subaccStr : subaccounts) { 0097 const auto subacc = m_file->account(subaccStr); 0098 0099 auto item = new QStandardItem(subacc.name()); // initialize first column of subaccount 0100 node->appendRow(item); // add subaccount row to node 0101 item->setEditable(false); 0102 0103 item->setData(node->data((int)Role::DisplayOrder), (int)Role::DisplayOrder); // inherit display order role from node 0104 0105 loadSubaccounts(item, favoriteAccountsItem, subacc.accountList()); // subaccount may have subaccounts as well 0106 0107 // set the account data after the children have been loaded 0108 const auto row = item->row(); 0109 setAccountData(node, row, subacc, m_columns); // initialize rest of columns of subaccount 0110 loadPreferredAccount(subacc, node, row, favoriteAccountsItem); // add to favourites node if preferred 0111 } 0112 } 0113 0114 /** 0115 * Note: this functions should only be called after the child account data has been set. 0116 */ 0117 void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns) 0118 { 0119 QStandardItem *cell; 0120 0121 auto getCell = [&, row](const auto column) { 0122 cell = node->child(row, column); // try to get QStandardItem 0123 if (!cell) { // it may be uninitialized 0124 cell = new QStandardItem; // so create one 0125 node->setChild(row, column, cell); // and add it under the node 0126 } 0127 }; 0128 0129 auto colNum = m_columns.indexOf(Column::Account); 0130 if (colNum == -1) 0131 return; 0132 getCell(colNum); 0133 auto font = cell->data(Qt::FontRole).value<QFont>(); 0134 // display the names of closed accounts with strikeout font 0135 if (account.isClosed() != font.strikeOut()) 0136 font.setStrikeOut(account.isClosed()); 0137 0138 if (columns.contains(Column::Account)) { 0139 // setting account column 0140 cell->setData(account.name(), Qt::DisplayRole); 0141 // cell->setData(QVariant::fromValue(account), (int)Role::Account); // is set in setAccountBalanceAndValue 0142 cell->setData(QVariant(account.id()), (int)Role::ID); 0143 cell->setData(QVariant(account.value("PreferredAccount") == QLatin1String("Yes")), (int)Role::Favorite); 0144 cell->setData(QVariant(QIcon(account.accountPixmap(m_reconciledAccount.id().isEmpty() ? false : account.id() == m_reconciledAccount.id(), 22))), 0145 Qt::DecorationRole); 0146 cell->setData(MyMoneyFile::instance()->accountToCategory(account.id(), true), (int)Role::FullName); 0147 cell->setData(font, Qt::FontRole); 0148 } 0149 0150 // Type 0151 if (columns.contains(Column::Type)) { 0152 colNum = m_columns.indexOf(Column::Type); 0153 if (colNum != -1) { 0154 getCell(colNum); 0155 cell->setData(account.accountTypeToString(account.accountType()), Qt::DisplayRole); 0156 cell->setData(font, Qt::FontRole); 0157 } 0158 } 0159 0160 // Account's number 0161 if (columns.contains(Column::AccountNumber)) { 0162 colNum = m_columns.indexOf(Column::AccountNumber); 0163 if (colNum != -1) { 0164 getCell(colNum); 0165 cell->setData(account.number(), Qt::DisplayRole); 0166 cell->setData(font, Qt::FontRole); 0167 } 0168 } 0169 0170 // Account's sort code 0171 if (columns.contains(Column::AccountSortCode)) { 0172 colNum = m_columns.indexOf(Column::AccountSortCode); 0173 if (colNum != -1) { 0174 getCell(colNum); 0175 cell->setData(account.value("iban"), Qt::DisplayRole); 0176 cell->setData(font, Qt::FontRole); 0177 } 0178 } 0179 0180 const auto checkMark = Icons::get(Icon::DialogOK); 0181 switch (account.accountType()) { 0182 case Account::Type::Income: 0183 case Account::Type::Expense: 0184 case Account::Type::Asset: 0185 case Account::Type::Liability: 0186 // Tax 0187 if (columns.contains(Column::Tax)) { 0188 colNum = m_columns.indexOf(Column::Tax); 0189 if (colNum != -1) { 0190 getCell(colNum); 0191 if (account.value("Tax").toLower() == "yes") 0192 cell->setData(checkMark, Qt::DecorationRole); 0193 else 0194 cell->setData(QIcon(), Qt::DecorationRole); 0195 } 0196 } 0197 0198 // VAT Account 0199 if (columns.contains(Column::VAT)) { 0200 colNum = m_columns.indexOf(Column::VAT); 0201 if (colNum != -1) { 0202 getCell(colNum); 0203 if (!account.value("VatAccount").isEmpty()) { 0204 const auto vatAccount = MyMoneyFile::instance()->account(account.value("VatAccount")); 0205 cell->setData(vatAccount.name(), Qt::DisplayRole); 0206 cell->setData(QVariant(Qt::AlignLeft | Qt::AlignVCenter), Qt::TextAlignmentRole); 0207 0208 // VAT Rate 0209 } else if (!account.value("VatRate").isEmpty()) { 0210 const auto vatRate = MyMoneyMoney(account.value("VatRate")) * MyMoneyMoney(100, 1); 0211 cell->setData(QString::fromLatin1("%1 %").arg(vatRate.formatMoney(QString(), 1)), Qt::DisplayRole); 0212 cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); 0213 0214 } else { 0215 cell->setData(QString(), Qt::DisplayRole); 0216 } 0217 } 0218 } 0219 0220 // CostCenter 0221 if (columns.contains(Column::CostCenter)) { 0222 colNum = m_columns.indexOf(Column::CostCenter); 0223 if (colNum != -1) { 0224 getCell(colNum); 0225 if (account.isCostCenterRequired()) 0226 cell->setData(checkMark, Qt::DecorationRole); 0227 else 0228 cell->setData(QIcon(), Qt::DecorationRole); 0229 } 0230 } 0231 break; 0232 default: 0233 break; 0234 } 0235 0236 // balance and value 0237 setAccountBalanceAndValue(node, row, account, columns); 0238 } 0239 0240 void setInstitutionTotalValue(QStandardItem *node, const int row) 0241 { 0242 const auto colInstitution = m_columns.indexOf(Column::Account); 0243 auto itInstitution = node->child(row, colInstitution); 0244 const auto valInstitution = childrenTotalValue(itInstitution, true); 0245 itInstitution->setData(QVariant::fromValue(valInstitution ), (int)Role::TotalValue); 0246 0247 const auto colTotalValue = m_columns.indexOf(Column::TotalValue); 0248 if (colTotalValue == -1) 0249 return; 0250 auto cell = node->child(row, colTotalValue); 0251 if (!cell) { 0252 cell = new QStandardItem; 0253 node->setChild(row, colTotalValue, cell); 0254 } 0255 const auto fontColor = KMyMoneySettings::schemeColor(valInstitution.isNegative() ? SchemeColor::Negative : SchemeColor::Positive); 0256 cell->setData(QVariant(fontColor), Qt::ForegroundRole); 0257 cell->setData(QVariant(itInstitution->data(Qt::FontRole).value<QFont>()), Qt::FontRole); 0258 cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); 0259 cell->setData(MyMoneyUtils::formatMoney(valInstitution, m_file->baseCurrency()), Qt::DisplayRole); 0260 } 0261 0262 void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns) 0263 { 0264 QStandardItem *cell; 0265 0266 auto getCell = [&, row](auto column) 0267 { 0268 cell = node->child(row, column); 0269 if (!cell) { 0270 cell = new QStandardItem; 0271 node->setChild(row, column, cell); 0272 } 0273 }; 0274 0275 // setting account column 0276 auto colNum = m_columns.indexOf(Column::Account); 0277 if (colNum == -1) 0278 return; 0279 getCell(colNum); 0280 0281 MyMoneyMoney accountBalance, accountValue, accountTotalValue; 0282 if (columns.contains(Column::Account)) { // update values only when requested 0283 accountBalance = balance(account); 0284 accountValue = value(account, accountBalance); 0285 accountTotalValue = childrenTotalValue(cell) + accountValue; 0286 cell->setData(QVariant::fromValue(account), (int)Role::Account); 0287 cell->setData(QVariant::fromValue(accountBalance), (int)Role::Balance); 0288 cell->setData(QVariant::fromValue(accountValue), (int)Role::Value); 0289 cell->setData(QVariant::fromValue(accountTotalValue), (int)Role::TotalValue); 0290 } else { // otherwise save up on tedious calculations 0291 accountBalance = cell->data((int)Role::Balance).value<MyMoneyMoney>(); 0292 accountValue = cell->data((int)Role::Value).value<MyMoneyMoney>(); 0293 accountTotalValue = cell->data((int)Role::TotalValue).value<MyMoneyMoney>(); 0294 } 0295 0296 const auto font = QVariant(cell->data(Qt::FontRole).value<QFont>()); 0297 const auto alignment = QVariant(Qt::AlignRight | Qt::AlignVCenter); 0298 0299 // setting total balance column 0300 if (columns.contains(Column::TotalBalance)) { 0301 colNum = m_columns.indexOf(Column::TotalBalance); 0302 if (colNum != -1) { 0303 const auto accountBalanceStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountBalance, m_file->security(account.currencyId()))); 0304 getCell(colNum); 0305 // only show the balance, if its a different security/currency 0306 if (m_file->security(account.currencyId()) != m_file->baseCurrency()) { 0307 cell->setData(accountBalanceStr, Qt::DisplayRole); 0308 } 0309 cell->setData(font, Qt::FontRole); 0310 cell->setData(alignment, Qt::TextAlignmentRole); 0311 } 0312 } 0313 0314 // setting posted value column 0315 if (columns.contains(Column::PostedValue)) { 0316 colNum = m_columns.indexOf(Column::PostedValue); 0317 if (colNum != -1) { 0318 const auto accountValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountValue, m_file->baseCurrency())); 0319 getCell(colNum); 0320 const auto fontColor = KMyMoneySettings::schemeColor(accountValue.isNegative() ? SchemeColor::Negative : SchemeColor::Positive); 0321 cell->setData(QVariant(fontColor), Qt::ForegroundRole); 0322 cell->setData(accountValueStr, Qt::DisplayRole); 0323 cell->setData(font, Qt::FontRole); 0324 cell->setData(alignment, Qt::TextAlignmentRole); 0325 } 0326 } 0327 0328 // setting total value column 0329 if (columns.contains(Column::TotalValue)) { 0330 colNum = m_columns.indexOf(Column::TotalValue); 0331 if (colNum != -1) { 0332 const auto accountTotalValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountTotalValue, m_file->baseCurrency())); 0333 getCell(colNum); 0334 const auto fontColor = KMyMoneySettings::schemeColor(accountTotalValue.isNegative() ? SchemeColor::Negative : SchemeColor::Positive); 0335 cell->setData(accountTotalValueStr, Qt::DisplayRole); 0336 cell->setData(font, Qt::FontRole); 0337 cell->setData(QVariant(fontColor), Qt::ForegroundRole); 0338 cell->setData(alignment, Qt::TextAlignmentRole); 0339 } 0340 } 0341 } 0342 0343 /** 0344 * Compute the balance of the given account. 0345 * 0346 * @param account The account for which the balance is being computed. 0347 */ 0348 MyMoneyMoney balance(const MyMoneyAccount &account) 0349 { 0350 MyMoneyMoney balance; 0351 // a closed account has a zero balance by definition 0352 if (!account.isClosed()) { 0353 // account.balance() is not compatible with stock accounts 0354 if (account.isInvest()) 0355 balance = m_file->balance(account.id()); 0356 else 0357 balance = account.balance(); 0358 } 0359 0360 // for income and liability accounts, we reverse the sign 0361 switch (account.accountGroup()) { 0362 case Account::Type::Income: 0363 case Account::Type::Liability: 0364 case Account::Type::Equity: 0365 balance = -balance; 0366 break; 0367 0368 default: 0369 break; 0370 } 0371 0372 return balance; 0373 } 0374 0375 /** 0376 * Compute the value of the given account using the provided balance. 0377 * The value is defined as the balance of the account converted to the base currency. 0378 * 0379 * @param account The account for which the value is being computed. 0380 * @param balance The balance which should be used. 0381 * 0382 * @see balance 0383 */ 0384 MyMoneyMoney value(const MyMoneyAccount &account, const MyMoneyMoney &balance) 0385 { 0386 if (account.isClosed()) 0387 return MyMoneyMoney(); 0388 0389 QList<MyMoneyPrice> prices; 0390 MyMoneySecurity security = m_file->baseCurrency(); 0391 try { 0392 if (account.isInvest()) { 0393 security = m_file->security(account.currencyId()); 0394 prices += m_file->price(account.currencyId(), security.tradingCurrency()); 0395 if (security.tradingCurrency() != m_file->baseCurrency().id()) { 0396 MyMoneySecurity sec = m_file->security(security.tradingCurrency()); 0397 prices += m_file->price(sec.id(), m_file->baseCurrency().id()); 0398 } 0399 } else if (account.currencyId() != m_file->baseCurrency().id()) { 0400 security = m_file->security(account.currencyId()); 0401 prices += m_file->price(account.currencyId(), m_file->baseCurrency().id()); 0402 } 0403 0404 } catch (const MyMoneyException &e) { 0405 qDebug() << Q_FUNC_INFO << " caught exception while adding " << account.name() << "[" << account.id() << "]: " << e.what(); 0406 } 0407 0408 MyMoneyMoney value = balance; 0409 { 0410 QList<MyMoneyPrice>::const_iterator it_p; 0411 QString securityID = account.currencyId(); 0412 for (it_p = prices.constBegin(); it_p != prices.constEnd(); ++it_p) { 0413 value = (value * (MyMoneyMoney::ONE / (*it_p).rate(securityID))).convertPrecision(m_file->security(securityID).pricePrecision()); 0414 if ((*it_p).from() == securityID) 0415 securityID = (*it_p).to(); 0416 else 0417 securityID = (*it_p).from(); 0418 } 0419 value = value.convert(m_file->baseCurrency().smallestAccountFraction()); 0420 } 0421 0422 return value; 0423 } 0424 0425 /** 0426 * Compute the total value of the child accounts of the given account. 0427 * Note that the value of the current account is not in this sum. Also, 0428 * before calling this function, the caller must make sure that the values 0429 * of all sub-account must be already in the model in the @ref Role::Value. 0430 * 0431 * @param index The index of the account in the model. 0432 * @see value 0433 */ 0434 MyMoneyMoney childrenTotalValue(const QStandardItem *node, const bool isInstitutionsModel = false) 0435 { 0436 MyMoneyMoney totalValue; 0437 if (!node) 0438 return totalValue; 0439 0440 for (auto i = 0; i < node->rowCount(); ++i) { 0441 const auto childNode = node->child(i, (int)Column::Account); 0442 if (childNode->hasChildren()) 0443 totalValue += childrenTotalValue(childNode, isInstitutionsModel); 0444 const auto data = childNode->data((int)Role::Value); 0445 if (data.isValid()) { 0446 auto value = data.value<MyMoneyMoney>(); 0447 if (isInstitutionsModel) { 0448 const auto account = childNode->data((int)Role::Account).value<MyMoneyAccount>(); 0449 if (account.accountGroup() == Account::Type::Liability) 0450 value = -value; 0451 } 0452 totalValue += value; 0453 } 0454 } 0455 return totalValue; 0456 } 0457 0458 /** 0459 * Function to get the item from an account id. 0460 * 0461 * @param parent The parent to localize the search in the child items of this parameter. 0462 * @param accountId Search based on this parameter. 0463 * 0464 * @return The item corresponding to the given account id, NULL if the account was not found. 0465 */ 0466 QStandardItem *itemFromAccountId(QStandardItem *parent, const QString &accountId) { 0467 auto const model = parent->model(); 0468 const auto list = model->match(model->index(0, 0, parent->index()), (int)Role::ID, QVariant(accountId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); 0469 if (!list.isEmpty()) 0470 return model->itemFromIndex(list.front()); 0471 // TODO: if not found at this item search for it in the model and if found reparent it. 0472 return nullptr; 0473 } 0474 0475 /** 0476 * Function to get the item from an account id without knowing it's parent item. 0477 * Note that for the accounts which have two items in the model (favorite accounts) 0478 * the account item which is not the child of the favorite accounts item is always returned. 0479 * 0480 * @param model The model in which to search. 0481 * @param accountId Search based on this parameter. 0482 * 0483 * @return The item corresponding to the given account id, NULL if the account was not found. 0484 */ 0485 QStandardItem *itemFromAccountId(QStandardItemModel *model, const QString &accountId) 0486 { 0487 const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(accountId), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); 0488 for (const auto& index : list) { 0489 // always return the account which is not the child of the favorite accounts item 0490 if (index.parent().data((int)Role::ID).toString() != AccountsModel::favoritesAccountId) 0491 return model->itemFromIndex(index); 0492 } 0493 return nullptr; 0494 } 0495 0496 AccountsModel *q_ptr; 0497 0498 /** 0499 * Used to load the accounts data. 0500 */ 0501 MyMoneyFile *m_file; 0502 /** 0503 * Used to emit the @ref netWorthChanged signal. 0504 */ 0505 MyMoneyMoney m_lastNetWorth; 0506 /** 0507 * Used to emit the @ref profitChanged signal. 0508 */ 0509 MyMoneyMoney m_lastProfit; 0510 /** 0511 * Used to set the reconciliation flag. 0512 */ 0513 MyMoneyAccount m_reconciledAccount; 0514 0515 QList<Column> m_columns; 0516 static const QString m_accountsModelConfGroup; 0517 static const QString m_accountsModelColumnSelection; 0518 }; 0519 0520 const QString AccountsModelPrivate::m_accountsModelConfGroup = QStringLiteral("AccountsModel"); 0521 const QString AccountsModelPrivate::m_accountsModelColumnSelection = QStringLiteral("ColumnSelection"); 0522 0523 const QString AccountsModel::favoritesAccountId(QStringLiteral("Favorites")); 0524 0525 /** 0526 * The constructor is private so that only the @ref Models object can create such an object. 0527 */ 0528 AccountsModel::AccountsModel(QObject *parent) : 0529 QStandardItemModel(parent), 0530 d_ptr(new AccountsModelPrivate(this)) 0531 { 0532 Q_D(AccountsModel); 0533 d->init(); 0534 } 0535 0536 AccountsModel::AccountsModel(AccountsModelPrivate &dd, QObject *parent) : 0537 QStandardItemModel(parent), 0538 d_ptr(&dd) 0539 { 0540 Q_D(AccountsModel); 0541 d->init(); 0542 } 0543 0544 AccountsModel::~AccountsModel() 0545 { 0546 Q_D(AccountsModel); 0547 delete d; 0548 } 0549 0550 /** 0551 * Perform the initial load of the model data 0552 * from the @ref MyMoneyFile. 0553 * 0554 */ 0555 void AccountsModel::load() 0556 { 0557 Q_D(AccountsModel); 0558 blockSignals(true); 0559 QStandardItem *rootItem = invisibleRootItem(); 0560 0561 QFont font; 0562 font.setBold(true); 0563 0564 // adding favourite accounts node 0565 auto favoriteAccountsItem = new QStandardItem(); 0566 favoriteAccountsItem->setEditable(false); 0567 rootItem->appendRow(favoriteAccountsItem); 0568 { 0569 QMap<int, QVariant> itemData; 0570 itemData[Qt::DisplayRole] = itemData[Qt::EditRole] = itemData[(int)Role::FullName] = i18n("Favorites"); 0571 itemData[Qt::FontRole] = font; 0572 itemData[Qt::DecorationRole] = Icons::get(Icon::BankAccount); 0573 itemData[(int)Role::ID] = favoritesAccountId; 0574 itemData[(int)Role::DisplayOrder] = 0; 0575 this->setItemData(favoriteAccountsItem->index(), itemData); 0576 } 0577 0578 // adding account categories (asset, liability, etc.) node 0579 const QVector <Account::Type> categories { 0580 Account::Type::Asset, Account::Type::Liability, 0581 Account::Type::Income, Account::Type::Expense, 0582 Account::Type::Equity 0583 }; 0584 0585 for (const auto category : categories) { 0586 MyMoneyAccount account; 0587 QString accountName; 0588 int displayOrder; 0589 0590 switch (category) { 0591 case Account::Type::Asset: 0592 // Asset accounts 0593 account = d->m_file->asset(); 0594 accountName = i18n("Asset accounts"); 0595 displayOrder = 1; 0596 break; 0597 case Account::Type::Liability: 0598 // Liability accounts 0599 account = d->m_file->liability(); 0600 accountName = i18n("Liability accounts"); 0601 displayOrder = 2; 0602 break; 0603 case Account::Type::Income: 0604 // Income categories 0605 account = d->m_file->income(); 0606 accountName = i18n("Income categories"); 0607 displayOrder = 3; 0608 break; 0609 case Account::Type::Expense: 0610 // Expense categories 0611 account = d->m_file->expense(); 0612 accountName = i18n("Expense categories"); 0613 displayOrder = 4; 0614 break; 0615 case Account::Type::Equity: 0616 // Equity accounts 0617 account = d->m_file->equity(); 0618 accountName = i18n("Equity accounts"); 0619 displayOrder = 5; 0620 break; 0621 default: 0622 continue; 0623 } 0624 0625 auto accountsItem = new QStandardItem(accountName); 0626 accountsItem->setEditable(false); 0627 rootItem->appendRow(accountsItem); 0628 0629 { 0630 QMap<int, QVariant> itemData; 0631 itemData[Qt::DisplayRole] = accountName; 0632 itemData[(int)Role::FullName] = itemData[Qt::EditRole] = QVariant::fromValue(MyMoneyFile::instance()->accountToCategory(account.id(), true)); 0633 itemData[Qt::FontRole] = font; 0634 itemData[(int)Role::DisplayOrder] = displayOrder; 0635 this->setItemData(accountsItem->index(), itemData); 0636 } 0637 0638 // adding accounts (specific bank/investment accounts) belonging to given accounts category 0639 const auto& accountList = account.accountList(); 0640 for (const auto& accStr : accountList) { 0641 const auto acc = d->m_file->account(accStr); 0642 0643 auto item = new QStandardItem(acc.name()); 0644 accountsItem->appendRow(item); 0645 item->setEditable(false); 0646 auto subaccountsStr = acc.accountList(); 0647 // filter out stocks with zero balance if requested by user 0648 for (auto subaccStr = subaccountsStr.begin(); subaccStr != subaccountsStr.end();) { 0649 const auto subacc = d->m_file->account(*subaccStr); 0650 if (subacc.isInvest() && KMyMoneySettings::hideZeroBalanceEquities() && subacc.balance().isZero()) 0651 subaccStr = subaccountsStr.erase(subaccStr); 0652 else 0653 ++subaccStr; 0654 } 0655 0656 // adding subaccounts (e.g. stocks under given investment account) belonging to given account 0657 d->loadSubaccounts(item, favoriteAccountsItem, subaccountsStr); 0658 const auto row = item->row(); 0659 d->setAccountData(accountsItem, row, acc, d->m_columns); 0660 d->loadPreferredAccount(acc, accountsItem, row, favoriteAccountsItem); 0661 } 0662 0663 d->setAccountData(rootItem, accountsItem->row(), account, d->m_columns); 0664 } 0665 0666 blockSignals(false); 0667 checkNetWorth(); 0668 checkProfit(); 0669 } 0670 0671 QModelIndex AccountsModel::accountById(const QString& id) const 0672 { 0673 QModelIndexList accountList = match(index(0, 0), 0674 (int)Role::ID, 0675 id, 0676 1, 0677 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); 0678 0679 if(accountList.count() == 1) { 0680 return accountList.first(); 0681 } 0682 return QModelIndex(); 0683 } 0684 0685 QList<Column> *AccountsModel::getColumns() 0686 { 0687 Q_D(AccountsModel); 0688 return &d->m_columns; 0689 } 0690 0691 void AccountsModel::setColumnVisibility(const Column column, const bool show) 0692 { 0693 Q_D(AccountsModel); 0694 const auto ixCol = d->m_columns.indexOf(column); // get column index in our column's map 0695 if (!show && ixCol != -1) { // start removing column row by row from bottom to up 0696 d->m_columns.removeOne(column); // remove it from our column's map 0697 blockSignals(true); // block signals to not emit resources consuming dataChanged 0698 for (auto i = 0; i < rowCount(); ++i) { 0699 // recursive lambda function to remove cell belonging to unwanted column from all rows 0700 auto removeCellFromRow = [=](auto &&self, QStandardItem *item) -> bool { 0701 for(auto j = 0; j < item->rowCount(); ++j) { 0702 auto childItem = item->child(j); 0703 if (childItem->hasChildren()) 0704 self(self, childItem); 0705 childItem->removeColumn(ixCol); 0706 } 0707 return true; 0708 }; 0709 0710 auto topItem = item(i); 0711 if (topItem->hasChildren()) 0712 removeCellFromRow(removeCellFromRow, topItem); 0713 topItem->removeColumn(ixCol); 0714 } 0715 blockSignals(false); // unblock signals, so model can update itself with new column 0716 removeColumn(ixCol); // remove column from invisible root item which triggers model's view update 0717 } else if (show && ixCol == -1) { // start inserting columns row by row from up to bottom (otherwise columns will be inserted automatically) 0718 auto model = qobject_cast<InstitutionsModel *>(this); 0719 const auto isInstitutionsModel = model ? true : false; // if it's institution's model, then don't set any data on institution nodes 0720 0721 auto newColPos = 0; 0722 for(; newColPos < d->m_columns.count(); ++newColPos) { 0723 if (d->m_columns.at(newColPos) > column) 0724 break; 0725 } 0726 d->m_columns.insert(newColPos, column); // insert columns according to enum order for cleanliness 0727 0728 insertColumn(newColPos); 0729 setHorizontalHeaderItem(newColPos, new QStandardItem(getHeaderName(column))); 0730 blockSignals(true); 0731 for (auto i = 0; i < rowCount(); ++i) { 0732 // recursive lambda function to remove cell belonging to unwanted column from all rows 0733 auto addCellToRow = [&, newColPos](auto &&self, QStandardItem *item) -> bool { 0734 for(auto j = 0; j < item->rowCount(); ++j) { 0735 auto childItem = item->child(j); 0736 childItem->insertColumns(newColPos, 1); 0737 if (childItem->hasChildren()) 0738 self(self, childItem); 0739 d->setAccountData(item, j, childItem->data((int)Role::Account).value<MyMoneyAccount>(), QList<Column> {column}); 0740 } 0741 return true; 0742 }; 0743 0744 auto topItem = item(i); 0745 topItem->insertColumns(newColPos, 1); 0746 if (topItem->hasChildren()) 0747 addCellToRow(addCellToRow, topItem); 0748 0749 if (isInstitutionsModel) 0750 d->setInstitutionTotalValue(invisibleRootItem(), i); 0751 else if (i !=0) // favourites node doesn't play well here, so exclude it from update 0752 d->setAccountData(invisibleRootItem(), i, topItem->data((int)Role::Account).value<MyMoneyAccount>(), QList<Column> {column}); 0753 } 0754 blockSignals(false); 0755 } 0756 } 0757 0758 QString AccountsModel::getHeaderName(const Column column) 0759 { 0760 switch(column) { 0761 case Column::Account: 0762 return i18n("Account"); 0763 case Column::Type: 0764 return i18n("Type"); 0765 case Column::Tax: 0766 return i18nc("Column heading for category in tax report", "Tax"); 0767 case Column::VAT: 0768 return i18nc("Column heading for VAT category", "VAT"); 0769 case Column::CostCenter: 0770 return i18nc("Column heading for Cost Center", "CC"); 0771 case Column::TotalBalance: 0772 return i18n("Total Balance"); 0773 case Column::PostedValue: 0774 return i18n("Posted Value"); 0775 case Column::TotalValue: 0776 return i18n("Total Value"); 0777 case Column::AccountNumber: 0778 return i18n("Number"); 0779 case Column::AccountSortCode: 0780 return i18nc("IBAN, SWIFT, etc.", "Sort Code"); 0781 default: 0782 return QString(); 0783 } 0784 } 0785 0786 /** 0787 * Check if netWorthChanged should be emitted. 0788 */ 0789 void AccountsModel::checkNetWorth() 0790 { 0791 Q_D(AccountsModel); 0792 // compute the net worth 0793 QModelIndexList assetList = match(index(0, 0), 0794 (int)Role::ID, 0795 MyMoneyFile::instance()->asset().id(), 0796 1, 0797 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); 0798 0799 QModelIndexList liabilityList = match(index(0, 0), 0800 (int)Role::ID, 0801 MyMoneyFile::instance()->liability().id(), 0802 1, 0803 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); 0804 0805 MyMoneyMoney netWorth; 0806 if (!assetList.isEmpty() && !liabilityList.isEmpty()) { 0807 const auto assetValue = data(assetList.front(), (int)Role::TotalValue); 0808 const auto liabilityValue = data(liabilityList.front(), (int)Role::TotalValue); 0809 0810 if (assetValue.isValid() && liabilityValue.isValid()) 0811 netWorth = assetValue.value<MyMoneyMoney>() - liabilityValue.value<MyMoneyMoney>(); 0812 } 0813 if (d->m_lastNetWorth != netWorth) { 0814 d->m_lastNetWorth = netWorth; 0815 emit netWorthChanged(QVariantList {QVariant::fromValue(d->m_lastNetWorth)}, eView::Intent::UpdateNetWorth); 0816 } 0817 } 0818 0819 /** 0820 * Check if profitChanged should be emitted. 0821 */ 0822 void AccountsModel::checkProfit() 0823 { 0824 Q_D(AccountsModel); 0825 // compute the profit 0826 const auto incomeList = match(index(0, 0), 0827 (int)Role::ID, 0828 MyMoneyFile::instance()->income().id(), 0829 1, 0830 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); 0831 0832 const auto expenseList = match(index(0, 0), 0833 (int)Role::ID, 0834 MyMoneyFile::instance()->expense().id(), 0835 1, 0836 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); 0837 0838 MyMoneyMoney profit; 0839 if (!incomeList.isEmpty() && !expenseList.isEmpty()) { 0840 const auto incomeValue = data(incomeList.front(), (int)Role::TotalValue); 0841 const auto expenseValue = data(expenseList.front(), (int)Role::TotalValue); 0842 0843 if (incomeValue.isValid() && expenseValue.isValid()) 0844 profit = incomeValue.value<MyMoneyMoney>() - expenseValue.value<MyMoneyMoney>(); 0845 } 0846 if (d->m_lastProfit != profit) { 0847 d->m_lastProfit = profit; 0848 emit profitChanged(QVariantList {QVariant::fromValue(d->m_lastProfit)}, eView::Intent::UpdateProfit); 0849 } 0850 } 0851 0852 MyMoneyMoney AccountsModel::accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance) 0853 { 0854 Q_D(AccountsModel); 0855 return d->value(account, balance); 0856 } 0857 0858 /** 0859 * This slot should be connected so that the model will be notified which account is being reconciled. 0860 */ 0861 void AccountsModel::slotReconcileAccount(const MyMoneyAccount &account, const QDate &reconciliationDate, const MyMoneyMoney &endingBalance) 0862 { 0863 Q_D(AccountsModel); 0864 Q_UNUSED(reconciliationDate) 0865 Q_UNUSED(endingBalance) 0866 if (d->m_reconciledAccount.id() != account.id()) { 0867 // first clear the flag of the old reconciliation account 0868 if (!d->m_reconciledAccount.id().isEmpty()) { 0869 const auto list = match(index(0, 0), (int)Role::ID, QVariant(d->m_reconciledAccount.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); 0870 for (const auto& index : list) 0871 setData(index, QVariant(QIcon(account.accountPixmap(false, 22))), Qt::DecorationRole); 0872 } 0873 0874 // then set the reconciliation flag of the new reconciliation account 0875 const auto list = match(index(0, 0), (int)Role::ID, QVariant(account.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); 0876 for (const auto& index : list) 0877 setData(index, QVariant(QIcon(account.accountPixmap(true, 22))), Qt::DecorationRole); 0878 d->m_reconciledAccount = account; 0879 } 0880 } 0881 0882 /** 0883 * Notify the model that an object has been added. An action is performed only if the object is an account. 0884 * 0885 */ 0886 void AccountsModel::slotObjectAdded(File::Object objType, const QString& id) 0887 { 0888 Q_D(AccountsModel); 0889 if (objType != File::Object::Account) 0890 return; 0891 0892 const auto account = MyMoneyFile::instance()->account(id); 0893 0894 auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId); 0895 auto parentAccountItem = d->itemFromAccountId(this, account.parentAccountId()); 0896 auto item = d->itemFromAccountId(parentAccountItem, account.id()); 0897 if (!item) { 0898 item = new QStandardItem(account.name()); 0899 parentAccountItem->appendRow(item); 0900 item->setEditable(false); 0901 } 0902 // load the sub-accounts if there are any - there could be sub accounts if this is an add operation 0903 // that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change 0904 d->loadSubaccounts(item, favoriteAccountsItem, account.accountList()); 0905 0906 const auto row = item->row(); 0907 d->setAccountData(parentAccountItem, row, account, d->m_columns); 0908 d->loadPreferredAccount(account, parentAccountItem, row, favoriteAccountsItem); 0909 0910 checkNetWorth(); 0911 checkProfit(); 0912 } 0913 0914 /** 0915 * Notify the model that an object has been modified. An action is performed only if the object is an account. 0916 * 0917 */ 0918 void AccountsModel::slotObjectModified(File::Object objType, const QString& id) 0919 { 0920 Q_D(AccountsModel); 0921 if (objType != File::Object::Account) 0922 return; 0923 0924 const auto account = MyMoneyFile::instance()->account(id); 0925 auto accountItem = d->itemFromAccountId(this, id); 0926 if (!accountItem) { 0927 qDebug() << "Unexpected null accountItem in AccountsModel::slotObjectModified"; 0928 return; 0929 } 0930 0931 const auto oldAccount = accountItem->data((int)Role::Account).value<MyMoneyAccount>(); 0932 if (oldAccount.parentAccountId() == account.parentAccountId()) { 0933 // the hierarchy did not change so update the account data 0934 auto parentAccountItem = accountItem->parent(); 0935 if (!parentAccountItem) 0936 parentAccountItem = this->invisibleRootItem(); 0937 const auto row = accountItem->row(); 0938 d->setAccountData(parentAccountItem, row, account, d->m_columns); 0939 // and the child of the favorite item if the account is a favorite account or it's favorite status has just changed 0940 if (auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId)) { 0941 if (account.value("PreferredAccount") == QLatin1String("Yes")) 0942 d->loadPreferredAccount(account, parentAccountItem, row, favoriteAccountsItem); 0943 else if (auto favItem = d->itemFromAccountId(favoriteAccountsItem, account.id())) 0944 favoriteAccountsItem->removeRow(favItem->row()); // it's not favorite anymore 0945 } 0946 } else { 0947 // this means that the hierarchy was changed - simulate this with a remove followed by and add operation 0948 slotObjectRemoved(File::Object::Account, oldAccount.id()); 0949 slotObjectAdded(File::Object::Account, id); 0950 } 0951 0952 checkNetWorth(); 0953 checkProfit(); 0954 } 0955 0956 /** 0957 * Notify the model that an object has been removed. An action is performed only if the object is an account. 0958 * 0959 */ 0960 void AccountsModel::slotObjectRemoved(File::Object objType, const QString& id) 0961 { 0962 if (objType != File::Object::Account) 0963 return; 0964 0965 const auto list = match(index(0, 0), (int)Role::ID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); 0966 for (const auto& index : list) 0967 removeRow(index.row(), index.parent()); 0968 0969 checkNetWorth(); 0970 checkProfit(); 0971 } 0972 0973 /** 0974 * Notify the model that the account balance has been changed. 0975 */ 0976 void AccountsModel::slotBalanceOrValueChanged(const MyMoneyAccount &account) 0977 { 0978 Q_D(AccountsModel); 0979 auto itParent = d->itemFromAccountId(this, account.id()); // get node of account in model 0980 auto isTopLevel = false; // it could be top-level but we don't know it yet 0981 while (itParent && !isTopLevel) { // loop in which we set total values and balances from the bottom to the top 0982 auto itCurrent = itParent; 0983 const auto accCurrent = d->m_file->account(itCurrent->data((int)Role::Account).value<MyMoneyAccount>().id()); 0984 if (accCurrent.id().isEmpty()) { // this is institution 0985 d->setInstitutionTotalValue(invisibleRootItem(), itCurrent->row()); 0986 break; // it's top-level node so nothing above that; 0987 } 0988 itParent = itCurrent->parent(); 0989 if (!itParent) { 0990 itParent = this->invisibleRootItem(); 0991 isTopLevel = true; 0992 } 0993 d->setAccountBalanceAndValue(itParent, itCurrent->row(), accCurrent, d->m_columns); 0994 } 0995 checkNetWorth(); 0996 checkProfit(); 0997 } 0998 0999 /** 1000 * The pimpl of the @ref InstitutionsModel derived from the pimpl of the @ref AccountsModel. 1001 */ 1002 class InstitutionsModelPrivate : public AccountsModelPrivate 1003 { 1004 public: 1005 InstitutionsModelPrivate(InstitutionsModel *qq) : 1006 AccountsModelPrivate(qq) 1007 { 1008 } 1009 1010 ~InstitutionsModelPrivate() override 1011 { 1012 } 1013 1014 /** 1015 * Function to get the institution item from an institution id. 1016 * 1017 * @param model The model in which to look for the item. 1018 * @param institutionId Search based on this parameter. 1019 * 1020 * @return The item corresponding to the given institution id, NULL if the institution was not found. 1021 */ 1022 QStandardItem *institutionItemFromId(QStandardItemModel *model, const QString &institutionId) { 1023 const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(institutionId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); 1024 if (!list.isEmpty()) 1025 return model->itemFromIndex(list.front()); 1026 return nullptr; // this should rarely fail as we add all institutions early on 1027 } 1028 1029 /** 1030 * Function to add the account item to it's corresponding institution item. 1031 * 1032 * @param model The model where to add the item. 1033 * @param account The account for which to create the item. 1034 * 1035 */ 1036 void loadInstitution(QStandardItemModel *model, const MyMoneyAccount &account) { 1037 if (!account.isAssetLiability() && !account.isInvest()) 1038 return; 1039 1040 // we've got account but don't know under which institution it should be added, so we find it out 1041 auto idInstitution = account.institutionId(); 1042 if (account.isInvest()) { // if it's stock account then... 1043 const auto investmentAccount = m_file->account(account.parentAccountId()); // ...get investment account it's under and... 1044 idInstitution = investmentAccount.institutionId(); // ...get institution from investment account 1045 } 1046 1047 auto itInstitution = institutionItemFromId(model, idInstitution); 1048 auto itAccount = itemFromAccountId(itInstitution, account.id()); // check if account already exists under institution 1049 // only stock accounts are added to their parent in the institutions view 1050 // this makes hierarchy maintenance a lot easier since the stock accounts 1051 // are the only ones that always have the same institution as their parent 1052 auto itInvestmentAccount = account.isInvest() ? itemFromAccountId(itInstitution, account.parentAccountId()) : nullptr; 1053 if (!itAccount) { 1054 itAccount = new QStandardItem(account.name()); 1055 if (itInvestmentAccount) // stock account nodes go under investment account nodes and... 1056 itInvestmentAccount->appendRow(itAccount); 1057 else if (itInstitution) // ...the rest goes under institution's node 1058 itInstitution->appendRow(itAccount); 1059 else 1060 return; 1061 itAccount->setEditable(false); 1062 } 1063 if (itInvestmentAccount) { 1064 setAccountData(itInvestmentAccount, itAccount->row(), account, m_columns); // set data for stock account node 1065 setAccountData(itInstitution, itInvestmentAccount->row(), m_file->account(account.parentAccountId()), m_columns); // set data for investment account node 1066 } else if (itInstitution) { 1067 setAccountData(itInstitution, itAccount->row(), account, m_columns); 1068 } 1069 } 1070 1071 /** 1072 * Function to add an institution item to the model. 1073 * 1074 * @param model The model in which to add the item. 1075 * @param institution The institution object which should be represented by the item. 1076 * 1077 */ 1078 void addInstitutionItem(QStandardItemModel *model, const MyMoneyInstitution &institution) { 1079 QFont font; 1080 font.setBold(true); 1081 auto itInstitution = new QStandardItem(Icons::get(Icon::Institution), institution.name()); 1082 itInstitution->setFont(font); 1083 itInstitution->setData(QVariant::fromValue(MyMoneyMoney()), (int)Role::TotalValue); 1084 itInstitution->setData(institution.id(), (int)Role::ID); 1085 itInstitution->setData(QVariant::fromValue(institution), (int)Role::Account); 1086 itInstitution->setData(6, (int)Role::DisplayOrder); 1087 itInstitution->setEditable(false); 1088 model->invisibleRootItem()->appendRow(itInstitution); 1089 setInstitutionTotalValue(model->invisibleRootItem(), itInstitution->row()); 1090 } 1091 }; 1092 1093 /** 1094 * The institution model contains the accounts grouped by institution. 1095 * 1096 */ 1097 InstitutionsModel::InstitutionsModel(QObject *parent) : 1098 AccountsModel(*new InstitutionsModelPrivate(this), parent) 1099 { 1100 } 1101 1102 InstitutionsModel::~InstitutionsModel() 1103 { 1104 } 1105 1106 /** 1107 * Perform the initial load of the model data 1108 * from the @ref MyMoneyFile. 1109 * 1110 */ 1111 void InstitutionsModel::load() 1112 { 1113 Q_D(InstitutionsModel); 1114 // create items for all the institutions 1115 auto institutionList = d->m_file->institutionList(); 1116 MyMoneyInstitution none; 1117 none.setName(i18n("Accounts with no institution assigned")); 1118 institutionList.append(none); 1119 for (const auto& institution : institutionList) // add all known institutions as top-level nodes 1120 d->addInstitutionItem(this, institution); 1121 1122 QList<MyMoneyAccount> accountsList; 1123 QList<MyMoneyAccount> stocksList; 1124 d->m_file->accountList(accountsList); 1125 for (const auto& account : accountsList) { // add account nodes under institution nodes... 1126 if (account.isInvest()) // ...but wait with stocks until investment accounts appear 1127 stocksList.append(account); 1128 else 1129 d->loadInstitution(this, account); 1130 } 1131 1132 for (const auto& stock : stocksList) { 1133 if (!(KMyMoneySettings::hideZeroBalanceEquities() && stock.balance().isZero())) 1134 d->loadInstitution(this, stock); 1135 } 1136 1137 for (auto i = 0 ; i < rowCount(); ++i) 1138 d->setInstitutionTotalValue(invisibleRootItem(), i); 1139 } 1140 1141 /** 1142 * Notify the model that an object has been added. An action is performed only if the object is an account or an institution. 1143 * 1144 */ 1145 void InstitutionsModel::slotObjectAdded(File::Object objType, const QString& id) 1146 { 1147 Q_D(InstitutionsModel); 1148 if (objType == File::Object::Institution) { 1149 // if an institution was added then add the item which will represent it 1150 const auto institution = MyMoneyFile::instance()->institution(id); 1151 d->addInstitutionItem(this, institution); 1152 } 1153 1154 if (objType != File::Object::Account) 1155 return; 1156 1157 // if an account was added then add the item which will represent it only for real accounts 1158 const auto account = MyMoneyFile::instance()->account(id); 1159 // nothing to do for root accounts and categories 1160 if (account.parentAccountId().isEmpty() || account.isIncomeExpense()) 1161 return; 1162 1163 // load the account into the institution 1164 d->loadInstitution(this, account); 1165 1166 // load the investment sub-accounts if there are any - there could be sub-accounts if this is an add operation 1167 // that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change 1168 const auto& sAccounts = account.accountList(); 1169 if (!sAccounts.isEmpty()) { 1170 QList<MyMoneyAccount> subAccounts; 1171 d->m_file->accountList(subAccounts, sAccounts); 1172 for (const auto& subAccount : subAccounts) { 1173 if (subAccount.isInvest()) { 1174 d->loadInstitution(this, subAccount); 1175 } 1176 } 1177 } 1178 } 1179 1180 /** 1181 * Notify the model that an object has been modified. An action is performed only if the object is an account or an institution. 1182 * 1183 */ 1184 void InstitutionsModel::slotObjectModified(File::Object objType, const QString& id) 1185 { 1186 Q_D(InstitutionsModel); 1187 if (objType == File::Object::Institution) { 1188 // if an institution was modified then modify the item which represents it 1189 const auto institution = MyMoneyFile::instance()->institution(id); 1190 if (auto institutionItem = d->institutionItemFromId(this, id)) { 1191 institutionItem->setData(institution.name(), Qt::DisplayRole); 1192 institutionItem->setData(QVariant::fromValue(institution), (int)Role::Account); 1193 institutionItem->setIcon(MyMoneyInstitution::pixmap()); 1194 } 1195 } 1196 1197 if (objType != File::Object::Account) 1198 return; 1199 1200 // if an account was modified then modify the item which represents it 1201 const auto account = MyMoneyFile::instance()->account(id); 1202 // nothing to do for root accounts, categories and equity accounts since they don't have a representation in this model 1203 if (account.parentAccountId().isEmpty() || account.isIncomeExpense() || account.accountType() == Account::Type::Equity) 1204 return; 1205 1206 auto accountItem = d->itemFromAccountId(this, account.id()); 1207 const auto oldAccount = accountItem->data((int)Role::Account).value<MyMoneyAccount>(); 1208 if (oldAccount.institutionId() == account.institutionId()) { 1209 // the hierarchy did not change so update the account data 1210 d->setAccountData(accountItem->parent(), accountItem->row(), account, d->m_columns); 1211 } else { 1212 // this means that the hierarchy was changed - simulate this with a remove followed by and add operation 1213 slotObjectRemoved(File::Object::Account, oldAccount.id()); 1214 slotObjectAdded(File::Object::Account, id); 1215 } 1216 } 1217 1218 /** 1219 * Notify the model that an object has been removed. An action is performed only if the object is an account or an institution. 1220 * 1221 */ 1222 void InstitutionsModel::slotObjectRemoved(File::Object objType, const QString& id) 1223 { 1224 Q_D(InstitutionsModel); 1225 if (objType == File::Object::Institution) { 1226 // if an institution was removed then remove the item which represents it 1227 if (auto itInstitution = d->institutionItemFromId(this, id)) 1228 removeRow(itInstitution->row(), itInstitution->index().parent()); 1229 } 1230 1231 if (objType != File::Object::Account) 1232 return; 1233 1234 // if an account was removed then remove the item which represents it and recompute the institution's value 1235 auto itAccount = d->itemFromAccountId(this, id); 1236 if (!itAccount) 1237 return; // this could happen if the account isIncomeExpense 1238 1239 const auto account = itAccount->data((int)Role::Account).value<MyMoneyAccount>(); 1240 if (auto itInstitution = d->itemFromAccountId(this, account.institutionId())) { 1241 AccountsModel::slotObjectRemoved(objType, id); 1242 d->setInstitutionTotalValue(invisibleRootItem(), itInstitution->row()); 1243 } 1244 }