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 }