1 /*
2 * Copyright 2010-2014 Cristian Oneț <onet.cristian@gmail.com>
3 * Copyright 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
4 * Copyright 2020 Robert Szczesiak <dev.rszczesiak@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "accountsmodel.h"
21
22 // ----------------------------------------------------------------------------
23 // QT Includes
24
25 #include <QIcon>
26
27 // ----------------------------------------------------------------------------
28 // KDE Includes
29
30 #include <KLocalizedString>
31
32 // ----------------------------------------------------------------------------
33 // Project Includes
34
35 #include "mymoneyutils.h"
36 #include "mymoneymoney.h"
37 #include "mymoneyexception.h"
38 #include "mymoneyfile.h"
39 #include "mymoneyinstitution.h"
40 #include "mymoneyaccount.h"
41 #include "mymoneysecurity.h"
42 #include "mymoneyprice.h"
43 #include "kmymoneysettings.h"
44 #include "icons.h"
45 #include "modelenums.h"
46 #include "mymoneyenums.h"
47 #include "viewenums.h"
48
49 using namespace Icons;
50 using namespace eAccountsModel;
51 using namespace eMyMoney;
52
53 class AccountsModelPrivate
54 {
55 Q_DECLARE_PUBLIC(AccountsModel)
56
57 public:
58 /**
59 * The pimpl.
60 */
AccountsModelPrivate(AccountsModel * qq)61 AccountsModelPrivate(AccountsModel *qq) :
62 q_ptr(qq),
63 m_file(MyMoneyFile::instance())
64 {
65 m_columns.append(Column::Account);
66 }
67
~AccountsModelPrivate()68 virtual ~AccountsModelPrivate()
69 {
70 }
71
init()72 void init()
73 {
74 Q_Q(AccountsModel);
75 QStringList headerLabels;
76 for (const auto& column : qAsConst(m_columns))
77 headerLabels.append(q->getHeaderName(column));
78 q->setHorizontalHeaderLabels(headerLabels);
79 }
80
loadPreferredAccount(const MyMoneyAccount & acc,QStandardItem * fromNode,const int row,QStandardItem * toNode)81 void loadPreferredAccount(const MyMoneyAccount &acc, QStandardItem *fromNode /*accounts' regular node*/, const int row, QStandardItem *toNode /*accounts' favourite node*/)
82 {
83 if (acc.value(QStringLiteral("PreferredAccount")) != QLatin1String("Yes"))
84 return;
85
86 auto favRow = toNode->rowCount();
87 if (auto favItem = itemFromAccountId(toNode, acc.id())) {
88 favRow = favItem->row();
89 toNode->removeRow(favRow);
90 }
91
92 auto itemToClone = fromNode->child(row);
93 if (itemToClone)
94 toNode->insertRow(favRow, itemToClone->clone());
95 }
96
97 /**
98 * Load all the sub-accounts recursively.
99 *
100 * @param model The model in which to load the data.
101 * @param accountsItem The item from the model of the parent account of the sub-accounts which are being loaded.
102 * @param favoriteAccountsItem The item of the favorites accounts groups so favorite accounts can be added here also.
103 * @param list The list of the account id's of the sub-accounts which are being loaded.
104 *
105 */
loadSubaccounts(QStandardItem * node,QStandardItem * favoriteAccountsItem,const QStringList & subaccounts)106 void loadSubaccounts(QStandardItem *node, QStandardItem *favoriteAccountsItem, const QStringList& subaccounts)
107 {
108 for (const auto& subaccStr : subaccounts) {
109 const auto subacc = m_file->account(subaccStr);
110
111 auto item = new QStandardItem(subacc.name()); // initialize first column of subaccount
112 node->appendRow(item); // add subaccount row to node
113 item->setEditable(false);
114
115 item->setData(node->data((int)Role::DisplayOrder), (int)Role::DisplayOrder); // inherit display order role from node
116
117 loadSubaccounts(item, favoriteAccountsItem, subacc.accountList()); // subaccount may have subaccounts as well
118
119 // set the account data after the children have been loaded
120 const auto row = item->row();
121 setAccountData(node, row, subacc, m_columns); // initialize rest of columns of subaccount
122 loadPreferredAccount(subacc, node, row, favoriteAccountsItem); // add to favourites node if preferred
123 }
124 }
125
126 /**
127 * Note: this functions should only be called after the child account data has been set.
128 */
setAccountData(QStandardItem * node,const int row,const MyMoneyAccount & account,const QList<Column> & columns)129 void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns)
130 {
131 QStandardItem *cell;
132
133 auto getCell = [&, row](const auto column) {
134 cell = node->child(row, column); // try to get QStandardItem
135 if (!cell) { // it may be uninitialized
136 cell = new QStandardItem; // so create one
137 node->setChild(row, column, cell); // and add it under the node
138 }
139 };
140
141 auto colNum = m_columns.indexOf(Column::Account);
142 if (colNum == -1)
143 return;
144 getCell(colNum);
145 auto font = cell->data(Qt::FontRole).value<QFont>();
146 // display the names of closed accounts with strikeout font
147 if (account.isClosed() != font.strikeOut())
148 font.setStrikeOut(account.isClosed());
149
150 if (columns.contains(Column::Account)) {
151 // setting account column
152 cell->setData(account.name(), Qt::DisplayRole);
153 // cell->setData(QVariant::fromValue(account), (int)Role::Account); // is set in setAccountBalanceAndValue
154 cell->setData(QVariant(account.id()), (int)Role::ID);
155 cell->setData(QVariant(account.value("PreferredAccount") == QLatin1String("Yes")), (int)Role::Favorite);
156 cell->setData(QVariant(QIcon(account.accountPixmap(m_reconciledAccount.id().isEmpty() ? false : account.id() == m_reconciledAccount.id()))), Qt::DecorationRole);
157 cell->setData(MyMoneyFile::instance()->accountToCategory(account.id(), true), (int)Role::FullName);
158 cell->setData(font, Qt::FontRole);
159 }
160
161 // Type
162 if (columns.contains(Column::Type)) {
163 colNum = m_columns.indexOf(Column::Type);
164 if (colNum != -1) {
165 getCell(colNum);
166 cell->setData(account.accountTypeToString(account.accountType()), Qt::DisplayRole);
167 cell->setData(font, Qt::FontRole);
168 }
169 }
170
171 // Account's number
172 if (columns.contains(Column::AccountNumber)) {
173 colNum = m_columns.indexOf(Column::AccountNumber);
174 if (colNum != -1) {
175 getCell(colNum);
176 cell->setData(account.number(), Qt::DisplayRole);
177 cell->setData(font, Qt::FontRole);
178 }
179 }
180
181 // Account's sort code
182 if (columns.contains(Column::AccountSortCode)) {
183 colNum = m_columns.indexOf(Column::AccountSortCode);
184 if (colNum != -1) {
185 getCell(colNum);
186 cell->setData(account.value("iban"), Qt::DisplayRole);
187 cell->setData(font, Qt::FontRole);
188 }
189 }
190
191 const auto checkMark = Icons::get(Icon::DialogOK);
192 switch (account.accountType()) {
193 case Account::Type::Income:
194 case Account::Type::Expense:
195 case Account::Type::Asset:
196 case Account::Type::Liability:
197 // Tax
198 if (columns.contains(Column::Tax)) {
199 colNum = m_columns.indexOf(Column::Tax);
200 if (colNum != -1) {
201 getCell(colNum);
202 if (account.value("Tax").toLower() == "yes")
203 cell->setData(checkMark, Qt::DecorationRole);
204 else
205 cell->setData(QIcon(), Qt::DecorationRole);
206 }
207 }
208
209 // VAT Account
210 if (columns.contains(Column::VAT)) {
211 colNum = m_columns.indexOf(Column::VAT);
212 if (colNum != -1) {
213 getCell(colNum);
214 if (!account.value("VatAccount").isEmpty()) {
215 const auto vatAccount = MyMoneyFile::instance()->account(account.value("VatAccount"));
216 cell->setData(vatAccount.name(), Qt::DisplayRole);
217 cell->setData(QVariant(Qt::AlignLeft | Qt::AlignVCenter), Qt::TextAlignmentRole);
218
219 // VAT Rate
220 } else if (!account.value("VatRate").isEmpty()) {
221 const auto vatRate = MyMoneyMoney(account.value("VatRate")) * MyMoneyMoney(100, 1);
222 cell->setData(QString::fromLatin1("%1 %").arg(vatRate.formatMoney(QString(), 1)), Qt::DisplayRole);
223 cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
224
225 } else {
226 cell->setData(QString(), Qt::DisplayRole);
227 }
228 }
229 }
230
231 // CostCenter
232 if (columns.contains(Column::CostCenter)) {
233 colNum = m_columns.indexOf(Column::CostCenter);
234 if (colNum != -1) {
235 getCell(colNum);
236 if (account.isCostCenterRequired())
237 cell->setData(checkMark, Qt::DecorationRole);
238 else
239 cell->setData(QIcon(), Qt::DecorationRole);
240 }
241 }
242 break;
243 default:
244 break;
245 }
246
247 // balance and value
248 setAccountBalanceAndValue(node, row, account, columns);
249 }
250
setInstitutionTotalValue(QStandardItem * node,const int row)251 void setInstitutionTotalValue(QStandardItem *node, const int row)
252 {
253 const auto colInstitution = m_columns.indexOf(Column::Account);
254 auto itInstitution = node->child(row, colInstitution);
255 const auto valInstitution = childrenTotalValue(itInstitution, true);
256 itInstitution->setData(QVariant::fromValue(valInstitution ), (int)Role::TotalValue);
257
258 const auto colTotalValue = m_columns.indexOf(Column::TotalValue);
259 if (colTotalValue == -1)
260 return;
261 auto cell = node->child(row, colTotalValue);
262 if (!cell) {
263 cell = new QStandardItem;
264 node->setChild(row, colTotalValue, cell);
265 }
266 const auto fontColor = KMyMoneySettings::schemeColor(valInstitution.isNegative() ? SchemeColor::Negative : SchemeColor::Positive);
267 cell->setData(QVariant(fontColor), Qt::ForegroundRole);
268 cell->setData(QVariant(itInstitution->data(Qt::FontRole).value<QFont>()), Qt::FontRole);
269 cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
270 cell->setData(MyMoneyUtils::formatMoney(valInstitution, m_file->baseCurrency()), Qt::DisplayRole);
271 }
272
setAccountBalanceAndValue(QStandardItem * node,const int row,const MyMoneyAccount & account,const QList<Column> & columns)273 void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList<Column> &columns)
274 {
275 QStandardItem *cell;
276
277 auto getCell = [&, row](auto column)
278 {
279 cell = node->child(row, column);
280 if (!cell) {
281 cell = new QStandardItem;
282 node->setChild(row, column, cell);
283 }
284 };
285
286 // setting account column
287 auto colNum = m_columns.indexOf(Column::Account);
288 if (colNum == -1)
289 return;
290 getCell(colNum);
291
292 MyMoneyMoney accountBalance, accountValue, accountTotalValue;
293 if (columns.contains(Column::Account)) { // update values only when requested
294 accountBalance = balance(account);
295 accountValue = value(account, accountBalance);
296 accountTotalValue = childrenTotalValue(cell) + accountValue;
297 cell->setData(QVariant::fromValue(account), (int)Role::Account);
298 cell->setData(QVariant::fromValue(accountBalance), (int)Role::Balance);
299 cell->setData(QVariant::fromValue(accountValue), (int)Role::Value);
300 cell->setData(QVariant::fromValue(accountTotalValue), (int)Role::TotalValue);
301 } else { // otherwise save up on tedious calculations
302 accountBalance = cell->data((int)Role::Balance).value<MyMoneyMoney>();
303 accountValue = cell->data((int)Role::Value).value<MyMoneyMoney>();
304 accountTotalValue = cell->data((int)Role::TotalValue).value<MyMoneyMoney>();
305 }
306
307 const auto font = QVariant(cell->data(Qt::FontRole).value<QFont>());
308 const auto alignment = QVariant(Qt::AlignRight | Qt::AlignVCenter);
309
310 // setting total balance column
311 if (columns.contains(Column::TotalBalance)) {
312 colNum = m_columns.indexOf(Column::TotalBalance);
313 if (colNum != -1) {
314 const auto accountBalanceStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountBalance, m_file->security(account.currencyId())));
315 getCell(colNum);
316 // only show the balance, if its a different security/currency
317 if (m_file->security(account.currencyId()) != m_file->baseCurrency()) {
318 cell->setData(accountBalanceStr, Qt::DisplayRole);
319 }
320 cell->setData(font, Qt::FontRole);
321 cell->setData(alignment, Qt::TextAlignmentRole);
322 }
323 }
324
325 // setting posted value column
326 if (columns.contains(Column::PostedValue)) {
327 colNum = m_columns.indexOf(Column::PostedValue);
328 if (colNum != -1) {
329 const auto accountValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountValue, m_file->baseCurrency()));
330 getCell(colNum);
331 const auto fontColor = KMyMoneySettings::schemeColor(accountValue.isNegative() ? SchemeColor::Negative : SchemeColor::Positive);
332 cell->setData(QVariant(fontColor), Qt::ForegroundRole);
333 cell->setData(accountValueStr, Qt::DisplayRole);
334 cell->setData(font, Qt::FontRole);
335 cell->setData(alignment, Qt::TextAlignmentRole);
336 }
337 }
338
339 // setting total value column
340 if (columns.contains(Column::TotalValue)) {
341 colNum = m_columns.indexOf(Column::TotalValue);
342 if (colNum != -1) {
343 const auto accountTotalValueStr = QVariant::fromValue(MyMoneyUtils::formatMoney(accountTotalValue, m_file->baseCurrency()));
344 getCell(colNum);
345 const auto fontColor = KMyMoneySettings::schemeColor(accountTotalValue.isNegative() ? SchemeColor::Negative : SchemeColor::Positive);
346 cell->setData(accountTotalValueStr, Qt::DisplayRole);
347 cell->setData(font, Qt::FontRole);
348 cell->setData(QVariant(fontColor), Qt::ForegroundRole);
349 cell->setData(alignment, Qt::TextAlignmentRole);
350 }
351 }
352 }
353
354 /**
355 * Compute the balance of the given account.
356 *
357 * @param account The account for which the balance is being computed.
358 */
balance(const MyMoneyAccount & account)359 MyMoneyMoney balance(const MyMoneyAccount &account)
360 {
361 MyMoneyMoney balance;
362 // a closed account has a zero balance by definition
363 if (!account.isClosed()) {
364 // account.balance() is not compatible with stock accounts
365 if (account.isInvest())
366 balance = m_file->balance(account.id());
367 else
368 balance = account.balance();
369 }
370
371 // for income and liability accounts, we reverse the sign
372 switch (account.accountGroup()) {
373 case Account::Type::Income:
374 case Account::Type::Liability:
375 case Account::Type::Equity:
376 balance = -balance;
377 break;
378
379 default:
380 break;
381 }
382
383 return balance;
384 }
385
386 /**
387 * Compute the value of the given account using the provided balance.
388 * The value is defined as the balance of the account converted to the base currency.
389 *
390 * @param account The account for which the value is being computed.
391 * @param balance The balance which should be used.
392 *
393 * @see balance
394 */
value(const MyMoneyAccount & account,const MyMoneyMoney & balance)395 MyMoneyMoney value(const MyMoneyAccount &account, const MyMoneyMoney &balance)
396 {
397 if (account.isClosed())
398 return MyMoneyMoney();
399
400 QList<MyMoneyPrice> prices;
401 MyMoneySecurity security = m_file->baseCurrency();
402 try {
403 if (account.isInvest()) {
404 security = m_file->security(account.currencyId());
405 prices += m_file->price(account.currencyId(), security.tradingCurrency());
406 if (security.tradingCurrency() != m_file->baseCurrency().id()) {
407 MyMoneySecurity sec = m_file->security(security.tradingCurrency());
408 prices += m_file->price(sec.id(), m_file->baseCurrency().id());
409 }
410 } else if (account.currencyId() != m_file->baseCurrency().id()) {
411 security = m_file->security(account.currencyId());
412 prices += m_file->price(account.currencyId(), m_file->baseCurrency().id());
413 }
414
415 } catch (const MyMoneyException &e) {
416 qDebug() << Q_FUNC_INFO << " caught exception while adding " << account.name() << "[" << account.id() << "]: " << e.what();
417 }
418
419 MyMoneyMoney value = balance;
420 {
421 QList<MyMoneyPrice>::const_iterator it_p;
422 QString securityID = account.currencyId();
423 for (it_p = prices.constBegin(); it_p != prices.constEnd(); ++it_p) {
424 value = (value * (MyMoneyMoney::ONE / (*it_p).rate(securityID))).convertPrecision(m_file->security(securityID).pricePrecision());
425 if ((*it_p).from() == securityID)
426 securityID = (*it_p).to();
427 else
428 securityID = (*it_p).from();
429 }
430 value = value.convert(m_file->baseCurrency().smallestAccountFraction());
431 }
432
433 return value;
434 }
435
436 /**
437 * Compute the total value of the child accounts of the given account.
438 * Note that the value of the current account is not in this sum. Also,
439 * before calling this function, the caller must make sure that the values
440 * of all sub-account must be already in the model in the @ref Role::Value.
441 *
442 * @param index The index of the account in the model.
443 * @see value
444 */
childrenTotalValue(const QStandardItem * node,const bool isInstitutionsModel=false)445 MyMoneyMoney childrenTotalValue(const QStandardItem *node, const bool isInstitutionsModel = false)
446 {
447 MyMoneyMoney totalValue;
448 if (!node)
449 return totalValue;
450
451 for (auto i = 0; i < node->rowCount(); ++i) {
452 const auto childNode = node->child(i, (int)Column::Account);
453 if (childNode->hasChildren())
454 totalValue += childrenTotalValue(childNode, isInstitutionsModel);
455 const auto data = childNode->data((int)Role::Value);
456 if (data.isValid()) {
457 auto value = data.value<MyMoneyMoney>();
458 if (isInstitutionsModel) {
459 const auto account = childNode->data((int)Role::Account).value<MyMoneyAccount>();
460 if (account.accountGroup() == Account::Type::Liability)
461 value = -value;
462 }
463 totalValue += value;
464 }
465 }
466 return totalValue;
467 }
468
469 /**
470 * Function to get the item from an account id.
471 *
472 * @param parent The parent to localize the search in the child items of this parameter.
473 * @param accountId Search based on this parameter.
474 *
475 * @return The item corresponding to the given account id, NULL if the account was not found.
476 */
itemFromAccountId(QStandardItem * parent,const QString & accountId)477 QStandardItem *itemFromAccountId(QStandardItem *parent, const QString &accountId) {
478 auto const model = parent->model();
479 const auto list = model->match(model->index(0, 0, parent->index()), (int)Role::ID, QVariant(accountId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
480 if (!list.isEmpty())
481 return model->itemFromIndex(list.front());
482 // TODO: if not found at this item search for it in the model and if found reparent it.
483 return nullptr;
484 }
485
486 /**
487 * Function to get the item from an account id without knowing it's parent item.
488 * Note that for the accounts which have two items in the model (favorite accounts)
489 * the account item which is not the child of the favorite accounts item is always returned.
490 *
491 * @param model The model in which to search.
492 * @param accountId Search based on this parameter.
493 *
494 * @return The item corresponding to the given account id, NULL if the account was not found.
495 */
itemFromAccountId(QStandardItemModel * model,const QString & accountId)496 QStandardItem *itemFromAccountId(QStandardItemModel *model, const QString &accountId)
497 {
498 const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(accountId), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
499 for (const auto& index : list) {
500 // always return the account which is not the child of the favorite accounts item
501 if (index.parent().data((int)Role::ID).toString() != AccountsModel::favoritesAccountId)
502 return model->itemFromIndex(index);
503 }
504 return nullptr;
505 }
506
507 AccountsModel *q_ptr;
508
509 /**
510 * Used to load the accounts data.
511 */
512 MyMoneyFile *m_file;
513 /**
514 * Used to emit the @ref netWorthChanged signal.
515 */
516 MyMoneyMoney m_lastNetWorth;
517 /**
518 * Used to emit the @ref profitChanged signal.
519 */
520 MyMoneyMoney m_lastProfit;
521 /**
522 * Used to set the reconciliation flag.
523 */
524 MyMoneyAccount m_reconciledAccount;
525
526 QList<Column> m_columns;
527 static const QString m_accountsModelConfGroup;
528 static const QString m_accountsModelColumnSelection;
529 };
530
531 const QString AccountsModelPrivate::m_accountsModelConfGroup = QStringLiteral("AccountsModel");
532 const QString AccountsModelPrivate::m_accountsModelColumnSelection = QStringLiteral("ColumnSelection");
533
534 const QString AccountsModel::favoritesAccountId(QStringLiteral("Favorites"));
535
536 /**
537 * The constructor is private so that only the @ref Models object can create such an object.
538 */
AccountsModel(QObject * parent)539 AccountsModel::AccountsModel(QObject *parent) :
540 QStandardItemModel(parent),
541 d_ptr(new AccountsModelPrivate(this))
542 {
543 Q_D(AccountsModel);
544 d->init();
545 }
546
AccountsModel(AccountsModelPrivate & dd,QObject * parent)547 AccountsModel::AccountsModel(AccountsModelPrivate &dd, QObject *parent) :
548 QStandardItemModel(parent),
549 d_ptr(&dd)
550 {
551 Q_D(AccountsModel);
552 d->init();
553 }
554
~AccountsModel()555 AccountsModel::~AccountsModel()
556 {
557 Q_D(AccountsModel);
558 delete d;
559 }
560
561 /**
562 * Perform the initial load of the model data
563 * from the @ref MyMoneyFile.
564 *
565 */
load()566 void AccountsModel::load()
567 {
568 Q_D(AccountsModel);
569 blockSignals(true);
570 QStandardItem *rootItem = invisibleRootItem();
571
572 QFont font;
573 font.setBold(true);
574
575 // adding favourite accounts node
576 auto favoriteAccountsItem = new QStandardItem();
577 favoriteAccountsItem->setEditable(false);
578 rootItem->appendRow(favoriteAccountsItem);
579 {
580 QMap<int, QVariant> itemData;
581 itemData[Qt::DisplayRole] = itemData[Qt::EditRole] = itemData[(int)Role::FullName] = i18n("Favorites");
582 itemData[Qt::FontRole] = font;
583 itemData[Qt::DecorationRole] = Icons::get(Icon::BankAccount);
584 itemData[(int)Role::ID] = favoritesAccountId;
585 itemData[(int)Role::DisplayOrder] = 0;
586 this->setItemData(favoriteAccountsItem->index(), itemData);
587 }
588
589 // adding account categories (asset, liability, etc.) node
590 const QVector <Account::Type> categories {
591 Account::Type::Asset, Account::Type::Liability,
592 Account::Type::Income, Account::Type::Expense,
593 Account::Type::Equity
594 };
595
596 for (const auto category : categories) {
597 MyMoneyAccount account;
598 QString accountName;
599 int displayOrder;
600
601 switch (category) {
602 case Account::Type::Asset:
603 // Asset accounts
604 account = d->m_file->asset();
605 accountName = i18n("Asset accounts");
606 displayOrder = 1;
607 break;
608 case Account::Type::Liability:
609 // Liability accounts
610 account = d->m_file->liability();
611 accountName = i18n("Liability accounts");
612 displayOrder = 2;
613 break;
614 case Account::Type::Income:
615 // Income categories
616 account = d->m_file->income();
617 accountName = i18n("Income categories");
618 displayOrder = 3;
619 break;
620 case Account::Type::Expense:
621 // Expense categories
622 account = d->m_file->expense();
623 accountName = i18n("Expense categories");
624 displayOrder = 4;
625 break;
626 case Account::Type::Equity:
627 // Equity accounts
628 account = d->m_file->equity();
629 accountName = i18n("Equity accounts");
630 displayOrder = 5;
631 break;
632 default:
633 continue;
634 }
635
636 auto accountsItem = new QStandardItem(accountName);
637 accountsItem->setEditable(false);
638 rootItem->appendRow(accountsItem);
639
640 {
641 QMap<int, QVariant> itemData;
642 itemData[Qt::DisplayRole] = accountName;
643 itemData[(int)Role::FullName] = itemData[Qt::EditRole] = QVariant::fromValue(MyMoneyFile::instance()->accountToCategory(account.id(), true));
644 itemData[Qt::FontRole] = font;
645 itemData[(int)Role::DisplayOrder] = displayOrder;
646 this->setItemData(accountsItem->index(), itemData);
647 }
648
649 // adding accounts (specific bank/investment accounts) belonging to given accounts category
650 const auto& accountList = account.accountList();
651 for (const auto& accStr : accountList) {
652 const auto acc = d->m_file->account(accStr);
653
654 auto item = new QStandardItem(acc.name());
655 accountsItem->appendRow(item);
656 item->setEditable(false);
657 auto subaccountsStr = acc.accountList();
658 // filter out stocks with zero balance if requested by user
659 for (auto subaccStr = subaccountsStr.begin(); subaccStr != subaccountsStr.end();) {
660 const auto subacc = d->m_file->account(*subaccStr);
661 if (subacc.isInvest() && KMyMoneySettings::hideZeroBalanceEquities() && subacc.balance().isZero())
662 subaccStr = subaccountsStr.erase(subaccStr);
663 else
664 ++subaccStr;
665 }
666
667 // adding subaccounts (e.g. stocks under given investment account) belonging to given account
668 d->loadSubaccounts(item, favoriteAccountsItem, subaccountsStr);
669 const auto row = item->row();
670 d->setAccountData(accountsItem, row, acc, d->m_columns);
671 d->loadPreferredAccount(acc, accountsItem, row, favoriteAccountsItem);
672 }
673
674 d->setAccountData(rootItem, accountsItem->row(), account, d->m_columns);
675 }
676
677 blockSignals(false);
678 checkNetWorth();
679 checkProfit();
680 }
681
accountById(const QString & id) const682 QModelIndex AccountsModel::accountById(const QString& id) const
683 {
684 QModelIndexList accountList = match(index(0, 0),
685 (int)Role::ID,
686 id,
687 1,
688 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
689
690 if(accountList.count() == 1) {
691 return accountList.first();
692 }
693 return QModelIndex();
694 }
695
getColumns()696 QList<Column> *AccountsModel::getColumns()
697 {
698 Q_D(AccountsModel);
699 return &d->m_columns;
700 }
701
setColumnVisibility(const Column column,const bool show)702 void AccountsModel::setColumnVisibility(const Column column, const bool show)
703 {
704 Q_D(AccountsModel);
705 const auto ixCol = d->m_columns.indexOf(column); // get column index in our column's map
706 if (!show && ixCol != -1) { // start removing column row by row from bottom to up
707 d->m_columns.removeOne(column); // remove it from our column's map
708 blockSignals(true); // block signals to not emit resources consuming dataChanged
709 for (auto i = 0; i < rowCount(); ++i) {
710 // recursive lambda function to remove cell belonging to unwanted column from all rows
711 auto removeCellFromRow = [=](auto &&self, QStandardItem *item) -> bool {
712 for(auto j = 0; j < item->rowCount(); ++j) {
713 auto childItem = item->child(j);
714 if (childItem->hasChildren())
715 self(self, childItem);
716 childItem->removeColumn(ixCol);
717 }
718 return true;
719 };
720
721 auto topItem = item(i);
722 if (topItem->hasChildren())
723 removeCellFromRow(removeCellFromRow, topItem);
724 topItem->removeColumn(ixCol);
725 }
726 blockSignals(false); // unblock signals, so model can update itself with new column
727 removeColumn(ixCol); // remove column from invisible root item which triggers model's view update
728 } else if (show && ixCol == -1) { // start inserting columns row by row from up to bottom (otherwise columns will be inserted automatically)
729 auto model = qobject_cast<InstitutionsModel *>(this);
730 const auto isInstitutionsModel = model ? true : false; // if it's institution's model, then don't set any data on institution nodes
731
732 auto newColPos = 0;
733 for(; newColPos < d->m_columns.count(); ++newColPos) {
734 if (d->m_columns.at(newColPos) > column)
735 break;
736 }
737 d->m_columns.insert(newColPos, column); // insert columns according to enum order for cleanliness
738
739 insertColumn(newColPos);
740 setHorizontalHeaderItem(newColPos, new QStandardItem(getHeaderName(column)));
741 blockSignals(true);
742 for (auto i = 0; i < rowCount(); ++i) {
743 // recursive lambda function to remove cell belonging to unwanted column from all rows
744 auto addCellToRow = [&, newColPos](auto &&self, QStandardItem *item) -> bool {
745 for(auto j = 0; j < item->rowCount(); ++j) {
746 auto childItem = item->child(j);
747 childItem->insertColumns(newColPos, 1);
748 if (childItem->hasChildren())
749 self(self, childItem);
750 d->setAccountData(item, j, childItem->data((int)Role::Account).value<MyMoneyAccount>(), QList<Column> {column});
751 }
752 return true;
753 };
754
755 auto topItem = item(i);
756 topItem->insertColumns(newColPos, 1);
757 if (topItem->hasChildren())
758 addCellToRow(addCellToRow, topItem);
759
760 if (isInstitutionsModel)
761 d->setInstitutionTotalValue(invisibleRootItem(), i);
762 else if (i !=0) // favourites node doesn't play well here, so exclude it from update
763 d->setAccountData(invisibleRootItem(), i, topItem->data((int)Role::Account).value<MyMoneyAccount>(), QList<Column> {column});
764 }
765 blockSignals(false);
766 }
767 }
768
getHeaderName(const Column column)769 QString AccountsModel::getHeaderName(const Column column)
770 {
771 switch(column) {
772 case Column::Account:
773 return i18n("Account");
774 case Column::Type:
775 return i18n("Type");
776 case Column::Tax:
777 return i18nc("Column heading for category in tax report", "Tax");
778 case Column::VAT:
779 return i18nc("Column heading for VAT category", "VAT");
780 case Column::CostCenter:
781 return i18nc("Column heading for Cost Center", "CC");
782 case Column::TotalBalance:
783 return i18n("Total Balance");
784 case Column::PostedValue:
785 return i18n("Posted Value");
786 case Column::TotalValue:
787 return i18n("Total Value");
788 case Column::AccountNumber:
789 return i18n("Number");
790 case Column::AccountSortCode:
791 return i18nc("IBAN, SWIFT, etc.", "Sort Code");
792 default:
793 return QString();
794 }
795 }
796
797 /**
798 * Check if netWorthChanged should be emitted.
799 */
checkNetWorth()800 void AccountsModel::checkNetWorth()
801 {
802 Q_D(AccountsModel);
803 // compute the net worth
804 QModelIndexList assetList = match(index(0, 0),
805 (int)Role::ID,
806 MyMoneyFile::instance()->asset().id(),
807 1,
808 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
809
810 QModelIndexList liabilityList = match(index(0, 0),
811 (int)Role::ID,
812 MyMoneyFile::instance()->liability().id(),
813 1,
814 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
815
816 MyMoneyMoney netWorth;
817 if (!assetList.isEmpty() && !liabilityList.isEmpty()) {
818 const auto assetValue = data(assetList.front(), (int)Role::TotalValue);
819 const auto liabilityValue = data(liabilityList.front(), (int)Role::TotalValue);
820
821 if (assetValue.isValid() && liabilityValue.isValid())
822 netWorth = assetValue.value<MyMoneyMoney>() - liabilityValue.value<MyMoneyMoney>();
823 }
824 if (d->m_lastNetWorth != netWorth) {
825 d->m_lastNetWorth = netWorth;
826 emit netWorthChanged(QVariantList {QVariant::fromValue(d->m_lastNetWorth)}, eView::Intent::UpdateNetWorth);
827 }
828 }
829
830 /**
831 * Check if profitChanged should be emitted.
832 */
checkProfit()833 void AccountsModel::checkProfit()
834 {
835 Q_D(AccountsModel);
836 // compute the profit
837 const auto incomeList = match(index(0, 0),
838 (int)Role::ID,
839 MyMoneyFile::instance()->income().id(),
840 1,
841 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
842
843 const auto expenseList = match(index(0, 0),
844 (int)Role::ID,
845 MyMoneyFile::instance()->expense().id(),
846 1,
847 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
848
849 MyMoneyMoney profit;
850 if (!incomeList.isEmpty() && !expenseList.isEmpty()) {
851 const auto incomeValue = data(incomeList.front(), (int)Role::TotalValue);
852 const auto expenseValue = data(expenseList.front(), (int)Role::TotalValue);
853
854 if (incomeValue.isValid() && expenseValue.isValid())
855 profit = incomeValue.value<MyMoneyMoney>() - expenseValue.value<MyMoneyMoney>();
856 }
857 if (d->m_lastProfit != profit) {
858 d->m_lastProfit = profit;
859 emit profitChanged(QVariantList {QVariant::fromValue(d->m_lastProfit)}, eView::Intent::UpdateProfit);
860 }
861 }
862
accountValue(const MyMoneyAccount & account,const MyMoneyMoney & balance)863 MyMoneyMoney AccountsModel::accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance)
864 {
865 Q_D(AccountsModel);
866 return d->value(account, balance);
867 }
868
869 /**
870 * This slot should be connected so that the model will be notified which account is being reconciled.
871 */
slotReconcileAccount(const MyMoneyAccount & account,const QDate & reconciliationDate,const MyMoneyMoney & endingBalance)872 void AccountsModel::slotReconcileAccount(const MyMoneyAccount &account, const QDate &reconciliationDate, const MyMoneyMoney &endingBalance)
873 {
874 Q_D(AccountsModel);
875 Q_UNUSED(reconciliationDate)
876 Q_UNUSED(endingBalance)
877 if (d->m_reconciledAccount.id() != account.id()) {
878 // first clear the flag of the old reconciliation account
879 if (!d->m_reconciledAccount.id().isEmpty()) {
880 const auto list = match(index(0, 0), (int)Role::ID, QVariant(d->m_reconciledAccount.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
881 for (const auto& index : list)
882 setData(index, QVariant(QIcon(account.accountPixmap(false))), Qt::DecorationRole);
883 }
884
885 // then set the reconciliation flag of the new reconciliation account
886 const auto list = match(index(0, 0), (int)Role::ID, QVariant(account.id()), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
887 for (const auto& index : list)
888 setData(index, QVariant(QIcon(account.accountPixmap(true))), Qt::DecorationRole);
889 d->m_reconciledAccount = account;
890 }
891 }
892
893 /**
894 * Notify the model that an object has been added. An action is performed only if the object is an account.
895 *
896 */
slotObjectAdded(File::Object objType,const QString & id)897 void AccountsModel::slotObjectAdded(File::Object objType, const QString& id)
898 {
899 Q_D(AccountsModel);
900 if (objType != File::Object::Account)
901 return;
902
903 const auto account = MyMoneyFile::instance()->account(id);
904
905 auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId);
906 auto parentAccountItem = d->itemFromAccountId(this, account.parentAccountId());
907 auto item = d->itemFromAccountId(parentAccountItem, account.id());
908 if (!item) {
909 item = new QStandardItem(account.name());
910 parentAccountItem->appendRow(item);
911 item->setEditable(false);
912 }
913 // load the sub-accounts if there are any - there could be sub accounts if this is an add operation
914 // that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change
915 d->loadSubaccounts(item, favoriteAccountsItem, account.accountList());
916
917 const auto row = item->row();
918 d->setAccountData(parentAccountItem, row, account, d->m_columns);
919 d->loadPreferredAccount(account, parentAccountItem, row, favoriteAccountsItem);
920
921 checkNetWorth();
922 checkProfit();
923 }
924
925 /**
926 * Notify the model that an object has been modified. An action is performed only if the object is an account.
927 *
928 */
slotObjectModified(File::Object objType,const QString & id)929 void AccountsModel::slotObjectModified(File::Object objType, const QString& id)
930 {
931 Q_D(AccountsModel);
932 if (objType != File::Object::Account)
933 return;
934
935 const auto account = MyMoneyFile::instance()->account(id);
936 auto accountItem = d->itemFromAccountId(this, id);
937 if (!accountItem) {
938 qDebug() << "Unexpected null accountItem in AccountsModel::slotObjectModified";
939 return;
940 }
941
942 const auto oldAccount = accountItem->data((int)Role::Account).value<MyMoneyAccount>();
943 if (oldAccount.parentAccountId() == account.parentAccountId()) {
944 // the hierarchy did not change so update the account data
945 auto parentAccountItem = accountItem->parent();
946 if (!parentAccountItem)
947 parentAccountItem = this->invisibleRootItem();
948 const auto row = accountItem->row();
949 d->setAccountData(parentAccountItem, row, account, d->m_columns);
950 // and the child of the favorite item if the account is a favorite account or it's favorite status has just changed
951 if (auto favoriteAccountsItem = d->itemFromAccountId(this, favoritesAccountId)) {
952 if (account.value("PreferredAccount") == QLatin1String("Yes"))
953 d->loadPreferredAccount(account, parentAccountItem, row, favoriteAccountsItem);
954 else if (auto favItem = d->itemFromAccountId(favoriteAccountsItem, account.id()))
955 favoriteAccountsItem->removeRow(favItem->row()); // it's not favorite anymore
956 }
957 } else {
958 // this means that the hierarchy was changed - simulate this with a remove followed by and add operation
959 slotObjectRemoved(File::Object::Account, oldAccount.id());
960 slotObjectAdded(File::Object::Account, id);
961 }
962
963 checkNetWorth();
964 checkProfit();
965 }
966
967 /**
968 * Notify the model that an object has been removed. An action is performed only if the object is an account.
969 *
970 */
slotObjectRemoved(File::Object objType,const QString & id)971 void AccountsModel::slotObjectRemoved(File::Object objType, const QString& id)
972 {
973 if (objType != File::Object::Account)
974 return;
975
976 const auto list = match(index(0, 0), (int)Role::ID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
977 for (const auto& index : list)
978 removeRow(index.row(), index.parent());
979
980 checkNetWorth();
981 checkProfit();
982 }
983
984 /**
985 * Notify the model that the account balance has been changed.
986 */
slotBalanceOrValueChanged(const MyMoneyAccount & account)987 void AccountsModel::slotBalanceOrValueChanged(const MyMoneyAccount &account)
988 {
989 Q_D(AccountsModel);
990 auto itParent = d->itemFromAccountId(this, account.id()); // get node of account in model
991 auto isTopLevel = false; // it could be top-level but we don't know it yet
992 while (itParent && !isTopLevel) { // loop in which we set total values and balances from the bottom to the top
993 auto itCurrent = itParent;
994 const auto accCurrent = d->m_file->account(itCurrent->data((int)Role::Account).value<MyMoneyAccount>().id());
995 if (accCurrent.id().isEmpty()) { // this is institution
996 d->setInstitutionTotalValue(invisibleRootItem(), itCurrent->row());
997 break; // it's top-level node so nothing above that;
998 }
999 itParent = itCurrent->parent();
1000 if (!itParent) {
1001 itParent = this->invisibleRootItem();
1002 isTopLevel = true;
1003 }
1004 d->setAccountBalanceAndValue(itParent, itCurrent->row(), accCurrent, d->m_columns);
1005 }
1006 checkNetWorth();
1007 checkProfit();
1008 }
1009
1010 /**
1011 * The pimpl of the @ref InstitutionsModel derived from the pimpl of the @ref AccountsModel.
1012 */
1013 class InstitutionsModelPrivate : public AccountsModelPrivate
1014 {
1015 public:
InstitutionsModelPrivate(InstitutionsModel * qq)1016 InstitutionsModelPrivate(InstitutionsModel *qq) :
1017 AccountsModelPrivate(qq)
1018 {
1019 }
1020
~InstitutionsModelPrivate()1021 ~InstitutionsModelPrivate() override
1022 {
1023 }
1024
1025 /**
1026 * Function to get the institution item from an institution id.
1027 *
1028 * @param model The model in which to look for the item.
1029 * @param institutionId Search based on this parameter.
1030 *
1031 * @return The item corresponding to the given institution id, NULL if the institution was not found.
1032 */
institutionItemFromId(QStandardItemModel * model,const QString & institutionId)1033 QStandardItem *institutionItemFromId(QStandardItemModel *model, const QString &institutionId) {
1034 const auto list = model->match(model->index(0, 0), (int)Role::ID, QVariant(institutionId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive));
1035 if (!list.isEmpty())
1036 return model->itemFromIndex(list.front());
1037 return nullptr; // this should rarely fail as we add all institutions early on
1038 }
1039
1040 /**
1041 * Function to add the account item to it's corresponding institution item.
1042 *
1043 * @param model The model where to add the item.
1044 * @param account The account for which to create the item.
1045 *
1046 */
loadInstitution(QStandardItemModel * model,const MyMoneyAccount & account)1047 void loadInstitution(QStandardItemModel *model, const MyMoneyAccount &account) {
1048 if (!account.isAssetLiability() && !account.isInvest())
1049 return;
1050
1051 // we've got account but don't know under which institution it should be added, so we find it out
1052 auto idInstitution = account.institutionId();
1053 if (account.isInvest()) { // if it's stock account then...
1054 const auto investmentAccount = m_file->account(account.parentAccountId()); // ...get investment account it's under and...
1055 idInstitution = investmentAccount.institutionId(); // ...get institution from investment account
1056 }
1057
1058 auto itInstitution = institutionItemFromId(model, idInstitution);
1059 auto itAccount = itemFromAccountId(itInstitution, account.id()); // check if account already exists under institution
1060 // only stock accounts are added to their parent in the institutions view
1061 // this makes hierarchy maintenance a lot easier since the stock accounts
1062 // are the only ones that always have the same institution as their parent
1063 auto itInvestmentAccount = account.isInvest() ? itemFromAccountId(itInstitution, account.parentAccountId()) : nullptr;
1064 if (!itAccount) {
1065 itAccount = new QStandardItem(account.name());
1066 if (itInvestmentAccount) // stock account nodes go under investment account nodes and...
1067 itInvestmentAccount->appendRow(itAccount);
1068 else if (itInstitution) // ...the rest goes under institution's node
1069 itInstitution->appendRow(itAccount);
1070 else
1071 return;
1072 itAccount->setEditable(false);
1073 }
1074 if (itInvestmentAccount) {
1075 setAccountData(itInvestmentAccount, itAccount->row(), account, m_columns); // set data for stock account node
1076 setAccountData(itInstitution, itInvestmentAccount->row(), m_file->account(account.parentAccountId()), m_columns); // set data for investment account node
1077 } else if (itInstitution) {
1078 setAccountData(itInstitution, itAccount->row(), account, m_columns);
1079 }
1080 }
1081
1082 /**
1083 * Function to add an institution item to the model.
1084 *
1085 * @param model The model in which to add the item.
1086 * @param institution The institution object which should be represented by the item.
1087 *
1088 */
addInstitutionItem(QStandardItemModel * model,const MyMoneyInstitution & institution)1089 void addInstitutionItem(QStandardItemModel *model, const MyMoneyInstitution &institution) {
1090 QFont font;
1091 font.setBold(true);
1092 auto itInstitution = new QStandardItem(Icons::get(Icon::Institution), institution.name());
1093 itInstitution->setFont(font);
1094 itInstitution->setData(QVariant::fromValue(MyMoneyMoney()), (int)Role::TotalValue);
1095 itInstitution->setData(institution.id(), (int)Role::ID);
1096 itInstitution->setData(QVariant::fromValue(institution), (int)Role::Account);
1097 itInstitution->setData(6, (int)Role::DisplayOrder);
1098 itInstitution->setEditable(false);
1099 model->invisibleRootItem()->appendRow(itInstitution);
1100 setInstitutionTotalValue(model->invisibleRootItem(), itInstitution->row());
1101 }
1102 };
1103
1104 /**
1105 * The institution model contains the accounts grouped by institution.
1106 *
1107 */
InstitutionsModel(QObject * parent)1108 InstitutionsModel::InstitutionsModel(QObject *parent) :
1109 AccountsModel(*new InstitutionsModelPrivate(this), parent)
1110 {
1111 }
1112
~InstitutionsModel()1113 InstitutionsModel::~InstitutionsModel()
1114 {
1115 }
1116
1117 /**
1118 * Perform the initial load of the model data
1119 * from the @ref MyMoneyFile.
1120 *
1121 */
load()1122 void InstitutionsModel::load()
1123 {
1124 Q_D(InstitutionsModel);
1125 // create items for all the institutions
1126 auto institutionList = d->m_file->institutionList();
1127 MyMoneyInstitution none;
1128 none.setName(i18n("Accounts with no institution assigned"));
1129 institutionList.append(none);
1130 for (const auto& institution : institutionList) // add all known institutions as top-level nodes
1131 d->addInstitutionItem(this, institution);
1132
1133 QList<MyMoneyAccount> accountsList;
1134 QList<MyMoneyAccount> stocksList;
1135 d->m_file->accountList(accountsList);
1136 for (const auto& account : accountsList) { // add account nodes under institution nodes...
1137 if (account.isInvest()) // ...but wait with stocks until investment accounts appear
1138 stocksList.append(account);
1139 else
1140 d->loadInstitution(this, account);
1141 }
1142
1143 for (const auto& stock : stocksList) {
1144 if (!(KMyMoneySettings::hideZeroBalanceEquities() && stock.balance().isZero()))
1145 d->loadInstitution(this, stock);
1146 }
1147
1148 for (auto i = 0 ; i < rowCount(); ++i)
1149 d->setInstitutionTotalValue(invisibleRootItem(), i);
1150 }
1151
1152 /**
1153 * Notify the model that an object has been added. An action is performed only if the object is an account or an institution.
1154 *
1155 */
slotObjectAdded(File::Object objType,const QString & id)1156 void InstitutionsModel::slotObjectAdded(File::Object objType, const QString& id)
1157 {
1158 Q_D(InstitutionsModel);
1159 if (objType == File::Object::Institution) {
1160 // if an institution was added then add the item which will represent it
1161 const auto institution = MyMoneyFile::instance()->institution(id);
1162 d->addInstitutionItem(this, institution);
1163 }
1164
1165 if (objType != File::Object::Account)
1166 return;
1167
1168 // if an account was added then add the item which will represent it only for real accounts
1169 const auto account = MyMoneyFile::instance()->account(id);
1170 // nothing to do for root accounts and categories
1171 if (account.parentAccountId().isEmpty() || account.isIncomeExpense())
1172 return;
1173
1174 // load the account into the institution
1175 d->loadInstitution(this, account);
1176
1177 // load the investment sub-accounts if there are any - there could be sub-accounts if this is an add operation
1178 // that was triggered in slotObjectModified on an already existing account which went trough a hierarchy change
1179 const auto& sAccounts = account.accountList();
1180 if (!sAccounts.isEmpty()) {
1181 QList<MyMoneyAccount> subAccounts;
1182 d->m_file->accountList(subAccounts, sAccounts);
1183 for (const auto& subAccount : subAccounts) {
1184 if (subAccount.isInvest()) {
1185 d->loadInstitution(this, subAccount);
1186 }
1187 }
1188 }
1189 }
1190
1191 /**
1192 * Notify the model that an object has been modified. An action is performed only if the object is an account or an institution.
1193 *
1194 */
slotObjectModified(File::Object objType,const QString & id)1195 void InstitutionsModel::slotObjectModified(File::Object objType, const QString& id)
1196 {
1197 Q_D(InstitutionsModel);
1198 if (objType == File::Object::Institution) {
1199 // if an institution was modified then modify the item which represents it
1200 const auto institution = MyMoneyFile::instance()->institution(id);
1201 if (auto institutionItem = d->institutionItemFromId(this, id)) {
1202 institutionItem->setData(institution.name(), Qt::DisplayRole);
1203 institutionItem->setData(QVariant::fromValue(institution), (int)Role::Account);
1204 institutionItem->setIcon(MyMoneyInstitution::pixmap());
1205 }
1206 }
1207
1208 if (objType != File::Object::Account)
1209 return;
1210
1211 // if an account was modified then modify the item which represents it
1212 const auto account = MyMoneyFile::instance()->account(id);
1213 // nothing to do for root accounts, categories and equity accounts since they don't have a representation in this model
1214 if (account.parentAccountId().isEmpty() || account.isIncomeExpense() || account.accountType() == Account::Type::Equity)
1215 return;
1216
1217 auto accountItem = d->itemFromAccountId(this, account.id());
1218 const auto oldAccount = accountItem->data((int)Role::Account).value<MyMoneyAccount>();
1219 if (oldAccount.institutionId() == account.institutionId()) {
1220 // the hierarchy did not change so update the account data
1221 d->setAccountData(accountItem->parent(), accountItem->row(), account, d->m_columns);
1222 } else {
1223 // this means that the hierarchy was changed - simulate this with a remove followed by and add operation
1224 slotObjectRemoved(File::Object::Account, oldAccount.id());
1225 slotObjectAdded(File::Object::Account, id);
1226 }
1227 }
1228
1229 /**
1230 * Notify the model that an object has been removed. An action is performed only if the object is an account or an institution.
1231 *
1232 */
slotObjectRemoved(File::Object objType,const QString & id)1233 void InstitutionsModel::slotObjectRemoved(File::Object objType, const QString& id)
1234 {
1235 Q_D(InstitutionsModel);
1236 if (objType == File::Object::Institution) {
1237 // if an institution was removed then remove the item which represents it
1238 if (auto itInstitution = d->institutionItemFromId(this, id))
1239 removeRow(itInstitution->row(), itInstitution->index().parent());
1240 }
1241
1242 if (objType != File::Object::Account)
1243 return;
1244
1245 // if an account was removed then remove the item which represents it and recompute the institution's value
1246 auto itAccount = d->itemFromAccountId(this, id);
1247 if (!itAccount)
1248 return; // this could happen if the account isIncomeExpense
1249
1250 const auto account = itAccount->data((int)Role::Account).value<MyMoneyAccount>();
1251 if (auto itInstitution = d->itemFromAccountId(this, account.institutionId())) {
1252 AccountsModel::slotObjectRemoved(objType, id);
1253 d->setInstitutionTotalValue(invisibleRootItem(), itInstitution->row());
1254 }
1255 }
1256