diff --git a/kmymoney/dialogs/kavailablecurrencydlg.ui b/kmymoney/dialogs/kavailablecurrencydlg.ui index ddba3fc9f..63048544e 100644 --- a/kmymoney/dialogs/kavailablecurrencydlg.ui +++ b/kmymoney/dialogs/kavailablecurrencydlg.ui @@ -1,102 +1,102 @@ KAvailableCurrencyDlg 0 0 400 516 Select currencies to add true 1 1 QAbstractItemView::NoEditTriggers QAbstractItemView::ExtendedSelection false true Name ID - Symbol + Symbol QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() KAvailableCurrencyDlg accept() 199 492 199 257 buttonBox rejected() KAvailableCurrencyDlg reject() 199 492 199 257 diff --git a/kmymoney/dialogs/kcurrencyeditdlg.ui b/kmymoney/dialogs/kcurrencyeditdlg.ui index 919c6b286..0e2a9428d 100644 --- a/kmymoney/dialogs/kcurrencyeditdlg.ui +++ b/kmymoney/dialogs/kcurrencyeditdlg.ui @@ -1,146 +1,146 @@ KCurrencyEditDlg 0 0 400 516 Currencies true 1 1 Qt::CustomContextMenu QAbstractItemView::ExtendedSelection false true Name ID - Symbol + Symbol Remove .. Add .. Edit Remove unused currencies .. Close Select as base currency m_currencyList m_addCurrencyButton m_removeCurrencyButton m_editCurrencyButton m_removeUnusedCurrencyButton m_selectBaseCurrencyButton m_closeButton m_closeButton clicked() KCurrencyEditDlg reject() 296 488 199 257 diff --git a/kmymoney/dialogs/kequitypriceupdatedlg.ui b/kmymoney/dialogs/kequitypriceupdatedlg.ui index c07429a2f..e1d2aae1a 100644 --- a/kmymoney/dialogs/kequitypriceupdatedlg.ui +++ b/kmymoney/dialogs/kequitypriceupdatedlg.ui @@ -1,197 +1,197 @@ Kevin Tambascio <ktambascio@users.sourceforge.net> KEquityPriceUpdateDlg 0 0 537 482 Update Stock and Currency Prices 350 0 List of known Equities, and the date they were last updated on. QFrame::StyledPanel QFrame::Plain true false true true 1 - From + From - To + To Configure Qt::Horizontal QSizePolicy::Expanding 21 20 Update All Update Selected Status: false true QDialogButtonBox::Cancel|QDialogButtonBox::Ok KTextEdit QTextEdit
ktextedit.h
KMyMoneyDateInput QWidget
kmymoneydateinput.h
1
buttonBox accepted() KEquityPriceUpdateDlg accept() 268 458 268 240 buttonBox rejected() KEquityPriceUpdateDlg reject() 268 458 268 240
diff --git a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp b/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp index 040db4aba..15a8d557b 100644 --- a/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp +++ b/kmymoney/dialogs/settings/ksettingsonlinequotes.cpp @@ -1,377 +1,377 @@ /* * Copyright 2005-2010 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "ksettingsonlinequotes.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_ksettingsonlinequotes.h" #include "kmymoney/converter/webpricequote.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "icons/icons.h" using namespace Icons; class KSettingsOnlineQuotesPrivate { Q_DISABLE_COPY(KSettingsOnlineQuotesPrivate) public: KSettingsOnlineQuotesPrivate() : ui(new Ui::KSettingsOnlineQuotes), m_quoteInEditing(false) { } ~KSettingsOnlineQuotesPrivate() { delete ui; } Ui::KSettingsOnlineQuotes *ui; QList m_resetList; WebPriceQuoteSource m_currentItem; bool m_quoteInEditing; }; KSettingsOnlineQuotes::KSettingsOnlineQuotes(QWidget *parent) : QWidget(parent), d_ptr(new KSettingsOnlineQuotesPrivate) { Q_D(KSettingsOnlineQuotes); d->ui->setupUi(this); QStringList groups = WebPriceQuote::quoteSources(); loadList(true /*updateResetList*/); d->ui->m_updateButton->setEnabled(false); d->ui->m_updateButton->setIcon(Icons::get(Icon::DialogOK)); d->ui->m_deleteButton->setIcon(Icons::get(Icon::EditDelete)); d->ui->m_newButton->setIcon(Icons::get(Icon::DocumentNew)); - d->ui->m_editIdentifyBy->addItem(i18n("Symbol"), WebPriceQuoteSource::identifyBy::Symbol); - d->ui->m_editIdentifyBy->addItem(i18n("Identification number"), WebPriceQuoteSource::identifyBy::IdentificationNumber); - d->ui->m_editIdentifyBy->addItem(i18n("Name"), WebPriceQuoteSource::identifyBy::Name); + d->ui->m_editIdentifyBy->addItem(i18nc("@item:inlistbox Stock", "Symbol"), WebPriceQuoteSource::identifyBy::Symbol); + d->ui->m_editIdentifyBy->addItem(i18nc("@item:inlistbox Stock", "Identification number"), WebPriceQuoteSource::identifyBy::IdentificationNumber); + d->ui->m_editIdentifyBy->addItem(i18nc("@item:inlistbox Stock", "Name"), WebPriceQuoteSource::identifyBy::Name); connect(d->ui->m_dumpCSVProfile, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotDumpCSVProfile); connect(d->ui->m_updateButton, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotUpdateEntry); connect(d->ui->m_newButton, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotNewEntry); connect(d->ui->m_deleteButton, &QAbstractButton::clicked, this, &KSettingsOnlineQuotes::slotDeleteEntry); connect(d->ui->m_quoteSourceList, &QListWidget::itemSelectionChanged, this, &KSettingsOnlineQuotes::slotLoadWidgets); connect(d->ui->m_quoteSourceList, &QListWidget::itemChanged, this, &KSettingsOnlineQuotes::slotEntryRenamed); connect(d->ui->m_quoteSourceList, &QListWidget::itemDoubleClicked, this, &KSettingsOnlineQuotes::slotStartRename); connect(d->ui->m_editURL, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); connect(d->ui->m_editCSVURL, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); connect(d->ui->m_editIdentifier, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); connect(d->ui->m_editIdentifyBy, static_cast(&QComboBox::currentIndexChanged), this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); connect(d->ui->m_editDate, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); connect(d->ui->m_editDateFormat, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); connect(d->ui->m_editPrice, &QLineEdit::textChanged, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); connect(d->ui->m_skipStripping, &QAbstractButton::toggled, this, static_cast(&KSettingsOnlineQuotes::slotEntryChanged)); } KSettingsOnlineQuotes::~KSettingsOnlineQuotes() { Q_D(KSettingsOnlineQuotes); delete d; } void KSettingsOnlineQuotes::loadList(const bool updateResetList) { Q_D(KSettingsOnlineQuotes); //disconnect the slot while items are being loaded and reconnect at the end disconnect(d->ui->m_quoteSourceList, &QListWidget::itemChanged, this, &KSettingsOnlineQuotes::slotEntryRenamed); d->m_quoteInEditing = false; QStringList groups = WebPriceQuote::quoteSources(); if (updateResetList) d->m_resetList.clear(); d->ui->m_quoteSourceList->clear(); QStringList::Iterator it; for (it = groups.begin(); it != groups.end(); ++it) { QListWidgetItem* item = new QListWidgetItem(*it); item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); d->ui->m_quoteSourceList->addItem(item); if (updateResetList) d->m_resetList += WebPriceQuoteSource(*it); } d->ui->m_quoteSourceList->sortItems(); QListWidgetItem* first = d->ui->m_quoteSourceList->item(0); if (first) d->ui->m_quoteSourceList->setCurrentItem(first); slotLoadWidgets(); d->ui->m_newButton->setEnabled((d->ui->m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly)).count() == 0); connect(d->ui->m_quoteSourceList, &QListWidget::itemChanged, this, &KSettingsOnlineQuotes::slotEntryRenamed); } void KSettingsOnlineQuotes::resetConfig() { Q_D(KSettingsOnlineQuotes); QStringList::ConstIterator it; QStringList groups = WebPriceQuote::quoteSources(); // delete all currently defined entries for (it = groups.constBegin(); it != groups.constEnd(); ++it) { WebPriceQuoteSource(*it).remove(); } // and write back the one's from the reset list QList::ConstIterator itr; for (itr = d->m_resetList.constBegin(); itr != d->m_resetList.constEnd(); ++itr) { (*itr).write(); } loadList(); } void KSettingsOnlineQuotes::slotLoadWidgets() { Q_D(KSettingsOnlineQuotes); d->m_quoteInEditing = false; QListWidgetItem* item = d->ui->m_quoteSourceList->currentItem(); d->ui->m_editURL->setEnabled(true); d->ui->m_editCSVURL->setEnabled(true); d->ui->m_editIdentifier->setEnabled(true); d->ui->m_editIdentifyBy->setEnabled(true); d->ui->m_editPrice->setEnabled(true); d->ui->m_editDate->setEnabled(true); d->ui->m_editDateFormat->setEnabled(true); d->ui->m_skipStripping->setEnabled(true); d->ui->m_dumpCSVProfile->setEnabled(true); d->ui->m_deleteButton->setEnabled(true); d->ui->m_editURL->setText(QString()); d->ui->m_editCSVURL->setText(QString()); d->ui->m_editIdentifier->setText(QString()); d->ui->m_editIdentifyBy->setCurrentIndex(WebPriceQuoteSource::identifyBy::Symbol); d->ui->m_editPrice->setText(QString()); d->ui->m_editDate->setText(QString()); d->ui->m_editDateFormat->setText(QString()); if (item) { d->m_currentItem = WebPriceQuoteSource(item->text()); d->ui->m_editURL->setText(d->m_currentItem.m_url); d->ui->m_editCSVURL->setText(d->m_currentItem.m_csvUrl); d->ui->m_editIdentifier->setText(d->m_currentItem.m_webID); d->ui->m_editIdentifyBy->setCurrentIndex(d->m_currentItem.m_webIDBy); d->ui->m_editPrice->setText(d->m_currentItem.m_price); d->ui->m_editDate->setText(d->m_currentItem.m_date); d->ui->m_editDateFormat->setText(d->m_currentItem.m_dateformat); d->ui->m_skipStripping->setChecked(d->m_currentItem.m_skipStripping); } else { d->ui->m_editURL->setEnabled(false); d->ui->m_editCSVURL->setEnabled(false); d->ui->m_editIdentifier->setEnabled(false); d->ui->m_editIdentifyBy->setEnabled(false); d->ui->m_editPrice->setEnabled(false); d->ui->m_editDate->setEnabled(false); d->ui->m_editDateFormat->setEnabled(false); d->ui->m_skipStripping->setEnabled(false); d->ui->m_dumpCSVProfile->setEnabled(false); d->ui->m_deleteButton->setEnabled(false); } d->ui->m_updateButton->setEnabled(false); } void KSettingsOnlineQuotes::slotEntryChanged() { Q_D(KSettingsOnlineQuotes); bool modified = d->ui->m_editURL->text() != d->m_currentItem.m_url || d->ui->m_editCSVURL->text() != d->m_currentItem.m_csvUrl || d->ui->m_editIdentifier->text() != d->m_currentItem.m_webID || d->ui->m_editIdentifyBy->currentData().toInt() != static_cast(d->m_currentItem.m_webIDBy) || d->ui->m_editDate->text() != d->m_currentItem.m_date || d->ui->m_editDateFormat->text() != d->m_currentItem.m_dateformat || d->ui->m_editPrice->text() != d->m_currentItem.m_price || d->ui->m_skipStripping->isChecked() != d->m_currentItem.m_skipStripping; d->ui->m_updateButton->setEnabled(modified); } void KSettingsOnlineQuotes::slotEntryChanged(int) { slotEntryChanged(); } void KSettingsOnlineQuotes::slotEntryChanged(const QString&) { slotEntryChanged(); } void KSettingsOnlineQuotes::slotEntryChanged(bool) { slotEntryChanged(); } void KSettingsOnlineQuotes::slotDumpCSVProfile() { Q_D(KSettingsOnlineQuotes); KSharedConfigPtr config = CSVImporterCore::configFile(); PricesProfile profile; profile.m_profileName = d->m_currentItem.m_name; profile.m_profileType = Profile::StockPrices; bool profileExists = false; bool writeProfile = true; if (profile.readSettings(config)) profileExists = true; else { profile.m_profileType = Profile::CurrencyPrices; if (profile.readSettings(config)) profileExists = true; } if (profileExists) writeProfile = (KMessageBox::questionYesNoCancel(this, i18n("CSV profile %1 already exists.
" "Do you want to overwrite it?", d->m_currentItem.m_name), i18n("CSV Profile Already Exists")) == KMessageBox::Yes ? true : false); if (writeProfile) { QMap quoteSources = WebPriceQuote::defaultCSVQuoteSources(); profile = quoteSources.value(d->m_currentItem.m_name); if (profile.m_profileName.compare(d->m_currentItem.m_name, Qt::CaseInsensitive) == 0) { profile.writeSettings(config); CSVImporterCore::profilesAction(profile.type(), ProfileAction::Add, profile.m_profileName, profile.m_profileName); } } CSVImporterCore::profilesAction(profile.type(), ProfileAction::UpdateLastUsed, profile.m_profileName, profile.m_profileName); } void KSettingsOnlineQuotes::slotUpdateEntry() { Q_D(KSettingsOnlineQuotes); d->m_currentItem.m_url = d->ui->m_editURL->text(); d->m_currentItem.m_csvUrl = d->ui->m_editCSVURL->text(); d->m_currentItem.m_webID = d->ui->m_editIdentifier->text(); d->m_currentItem.m_webIDBy = static_cast(d->ui->m_editIdentifyBy->currentData().toInt()); d->m_currentItem.m_date = d->ui->m_editDate->text(); d->m_currentItem.m_dateformat = d->ui->m_editDateFormat->text(); d->m_currentItem.m_price = d->ui->m_editPrice->text(); d->m_currentItem.m_skipStripping = d->ui->m_skipStripping->isChecked(); d->m_currentItem.write(); slotEntryChanged(); } void KSettingsOnlineQuotes::slotNewEntry() { Q_D(KSettingsOnlineQuotes); WebPriceQuoteSource newSource(i18n("New Quote Source")); newSource.write(); loadList(); QListWidgetItem* item = d->ui->m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly).at(0); if (item) { d->ui->m_quoteSourceList->setCurrentItem(item); slotLoadWidgets(); } } void KSettingsOnlineQuotes::slotDeleteEntry() { Q_D(KSettingsOnlineQuotes); // first check if no security is using this online source auto securities = MyMoneyFile::instance()->securityList(); foreach(const auto security, securities) { if (security.value(QStringLiteral("kmm-online-source")).compare(d->m_currentItem.m_name) == 0) { if (KMessageBox::questionYesNo(this, i18n("Security %1 uses this quote source.
" "Do you really want to remove it?", security.name()), i18n("Delete quote source")) == KMessageBox::Yes) break; // webpricequote can handle missing online quotes, so proceed without any extra action else return; } } // remove online source from webpricequote... d->m_currentItem.remove(); // ...and from setting's list auto row = d->ui->m_quoteSourceList->currentRow(); QListWidgetItem *item = d->ui->m_quoteSourceList->takeItem(row); if (item) delete item; item = nullptr; int count = d->ui->m_quoteSourceList->count(); if (row < count) // select next available entry... item = d->ui->m_quoteSourceList->item(row); else if (row >= count && count > 0) // ...or last entry if this was the last entry... item = d->ui->m_quoteSourceList->item(count - 1); if (item) { d->ui->m_quoteSourceList->setCurrentItem(item); slotLoadWidgets(); } } void KSettingsOnlineQuotes::slotStartRename(QListWidgetItem* item) { Q_D(KSettingsOnlineQuotes); d->m_quoteInEditing = true; d->ui->m_quoteSourceList->editItem(item); } void KSettingsOnlineQuotes::slotEntryRenamed(QListWidgetItem* item) { Q_D(KSettingsOnlineQuotes); //if there is no current item selected, exit if (d->m_quoteInEditing == false || !d->ui->m_quoteSourceList->currentItem() || item != d->ui->m_quoteSourceList->currentItem()) return; d->m_quoteInEditing = false; QString text = item->text(); int nameCount = 0; for (auto i = 0; i < d->ui->m_quoteSourceList->count(); ++i) { if (d->ui->m_quoteSourceList->item(i)->text() == text) ++nameCount; } // Make sure we get a non-empty and unique name if (text.length() > 0 && nameCount == 1) { d->m_currentItem.rename(text); } else { item->setText(d->m_currentItem.m_name); } d->ui->m_quoteSourceList->sortItems(); d->ui->m_newButton->setEnabled(d->ui->m_quoteSourceList->findItems(i18n("New Quote Source"), Qt::MatchExactly).count() == 0); } diff --git a/kmymoney/models/equitiesmodel.cpp b/kmymoney/models/equitiesmodel.cpp index 121472564..15e18e8e7 100644 --- a/kmymoney/models/equitiesmodel.cpp +++ b/kmymoney/models/equitiesmodel.cpp @@ -1,494 +1,494 @@ /* * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "equitiesmodel.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneyenums.h" class EquitiesModel::Private { public: Private() : m_file(MyMoneyFile::instance()) { QVector columns {Column::Equity, Column::Symbol, Column::Value, Column::Quantity, Column::Price}; foreach (auto const column, columns) m_columns.append(column); } ~Private() {} void loadInvestmentAccount(QStandardItem *node, const MyMoneyAccount &invAcc) { auto itInvAcc = new QStandardItem(invAcc.name()); node->appendRow(itInvAcc); // investment account is meant to be added under root item itInvAcc->setEditable(false); itInvAcc->setColumnCount(m_columns.count()); setAccountData(node, itInvAcc->row(), invAcc, m_columns); foreach (const auto strStkAcc, invAcc.accountList()) { // only stock or bond accounts are expected here auto stkAcc = m_file->account(strStkAcc); auto itStkAcc = new QStandardItem(strStkAcc); itStkAcc->setEditable(false); itInvAcc->appendRow(itStkAcc); setAccountData(itInvAcc, itStkAcc->row(), stkAcc, m_columns); } } void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) { QStandardItem *cell; auto getCell = [&, row](const auto column) { cell = node->child(row, column); // try to get QStandardItem if (!cell) { // it may be uninitialized cell = new QStandardItem; // so create one node->setChild(row, column, cell); // and add it under the node cell->setEditable(false); // and don't forget that it's non-editable } }; auto colNum = m_columns.indexOf(Column::Equity); if (colNum == -1) return; // Equity getCell(colNum); if (columns.contains(Column::Equity)) { cell->setData(account.name(), Qt::DisplayRole); cell->setData(account.id(), Role::EquityID); cell->setData(account.currencyId(), Role::SecurityID); } if (account.accountType() == eMyMoney::Account::Type::Investment) // investments accounts are not meant to be displayed, so stop here return; // Display the name of the equity with strikeout font in case it is closed auto font = cell->data(Qt::FontRole).value(); if (account.isClosed() != font.strikeOut()) { font.setStrikeOut(account.isClosed()); cell->setData(font, Qt::FontRole); } // Symbol if (columns.contains(Column::Symbol)) { colNum = m_columns.indexOf(Column::Symbol); if (colNum != -1) { auto security = m_file->security(account.currencyId()); getCell(colNum); cell->setData(security.tradingSymbol(), Qt::DisplayRole); } } setAccountBalanceAndValue(node, row, account, columns); } void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) { QStandardItem *cell; auto getCell = [&, row](const auto column) { cell = node->child(row, column); // try to get QStandardItem if (!cell) { // it may be uninitialized cell = new QStandardItem; // so create one node->setChild(row, column, cell); // and add it under the node cell->setEditable(false); // and don't forget that it's non-editable } }; auto colNum = m_columns.indexOf(Column::Equity); if (colNum == -1) return; auto balance = m_file->balance(account.id()); auto security = m_file->security(account.currencyId()); auto tradingCurrency = m_file->security(security.tradingCurrency()); auto price = m_file->price(account.currencyId(), tradingCurrency.id()); // Value if (columns.contains(Column::Value)) { colNum = m_columns.indexOf(Column::Value); if (colNum != -1) { getCell(colNum); if (price.isValid()) { auto prec = MyMoneyMoney::denomToPrec(tradingCurrency.smallestAccountFraction()); auto value = balance * price.rate(tradingCurrency.id()); auto strValue = QVariant(value.formatMoney(tradingCurrency.tradingSymbol(), prec)); cell->setData(strValue, Qt::DisplayRole); } else { cell->setData(QVariant("---"), Qt::DisplayRole); } cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); } } // Quantity if (columns.contains(Column::Quantity)) { colNum = m_columns.indexOf(Column::Quantity); if (colNum != -1) { getCell(colNum); auto prec = MyMoneyMoney::denomToPrec(security.smallestAccountFraction()); auto strQuantity = QVariant(balance.formatMoney(QString(), prec)); cell->setData(strQuantity, Qt::DisplayRole); cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); } } // Price if (columns.contains(Column::Price)) { colNum = m_columns.indexOf(Column::Price); if (colNum != -1) { getCell(colNum); if (price.isValid()) { auto prec = security.pricePrecision(); auto strPrice = QVariant(price.rate(tradingCurrency.id()).formatMoney(tradingCurrency.tradingSymbol(), prec)); cell->setData(strPrice, Qt::DisplayRole); } else { cell->setData(QVariant("---"), Qt::DisplayRole); } cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); } } } QStandardItem *itemFromId(QStandardItemModel *model, const QString &id, const Role role) { const auto itemList = model->match(model->index(0, 0), role, QVariant(id), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); if (!itemList.isEmpty()) return model->itemFromIndex(itemList.first()); return nullptr; } MyMoneyFile *m_file; QList m_columns; }; EquitiesModel::EquitiesModel(QObject *parent) : QStandardItemModel(parent), d(new Private) { init(); } EquitiesModel::~EquitiesModel() { delete d; } void EquitiesModel::init() { QStringList headerLabels; foreach (const auto column, d->m_columns) headerLabels.append(getHeaderName(column)); setHorizontalHeaderLabels(headerLabels); } void EquitiesModel::load() { this->blockSignals(true); auto rootItem = invisibleRootItem(); QList accList; d->m_file->accountList(accList); // get all available accounts foreach (const auto acc, accList) if (acc.accountType() == eMyMoney::Account::Type::Investment) // but add only investment accounts (and its children) to the model d->loadInvestmentAccount(rootItem, acc); this->blockSignals(false); } /** * Notify the model that an object has been added. An action is performed only if the object is an account. */ void EquitiesModel::slotObjectAdded(eMyMoney::File::Object objType, const QString& id) { // check whether change is about accounts if (objType != eMyMoney::File::Object::Account) return; // check whether change is about either investment or stock account const auto acc = MyMoneyFile::instance()->account(id); if (acc.accountType() != eMyMoney::Account::Type::Investment && acc.accountType() != eMyMoney::Account::Type::Stock) return; auto itAcc = d->itemFromId(this, id, Role::EquityID); QStandardItem *itParentAcc; if (acc.accountType() == eMyMoney::Account::Type::Investment) // if it's investment account then its parent is root item itParentAcc = invisibleRootItem(); else // otherwise it's stock account and its parent is investment account itParentAcc = d->itemFromId(this, acc.parentAccountId(), Role::InvestmentID); // if account doesn't exist in model then add it if (!itAcc) { itAcc = new QStandardItem(acc.name()); itParentAcc->appendRow(itAcc); itAcc->setEditable(false); } d->setAccountData(itParentAcc, itAcc->row(), acc, d->m_columns); } /** * Notify the model that an object has been modified. An action is performed only if the object is an account. */ void EquitiesModel::slotObjectModified(eMyMoney::File::Object objType, const QString& id) { MyMoneyAccount acc; QStandardItem *itAcc; switch (objType) { case eMyMoney::File::Object::Account: { auto tmpAcc = MyMoneyFile::instance()->account(id); if (tmpAcc.accountType() != eMyMoney::Account::Type::Stock) return; acc = MyMoneyAccount(tmpAcc); itAcc = d->itemFromId(this, acc.id(), Role::EquityID); break; } case eMyMoney::File::Object::Security: { auto sec = MyMoneyFile::instance()->security(id); // in case we hit a currency, we simply bail out here // as there is nothing to do for us if(sec.isCurrency()) return; itAcc = d->itemFromId(this, sec.id(), Role::SecurityID); if (!itAcc) return; const auto idAcc = itAcc->data(Role::EquityID).toString(); acc = d->m_file->account(idAcc); break; } default: return; } auto itParentAcc = d->itemFromId(this, acc.parentAccountId(), Role::InvestmentID); // in case something went wrong, we bail out if(itParentAcc == nullptr) { qWarning() << "EquitiesModel::slotObjectModified: itParentAcc == 0"; return; } auto modelID = itParentAcc->data(Role::InvestmentID).toString(); // get parent account from model if (modelID == acc.parentAccountId()) { // and if it matches with those from file then modify only d->setAccountData(itParentAcc, itAcc->row(), acc, d->m_columns); } else { // and if not then reparent slotObjectRemoved(eMyMoney::File::Object::Account, acc.id()); slotObjectAdded(eMyMoney::File::Object::Account, id); } } /** * Notify the model that an object has been removed. An action is performed only if the object is an account. * */ void EquitiesModel::slotObjectRemoved(eMyMoney::File::Object objType, const QString& id) { if (objType != eMyMoney::File::Object::Account) return; const auto indexList = match(index(0, 0), Role::EquityID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); foreach (const auto index, indexList) removeRow(index.row(), index.parent()); } /** * Notify the model that the account balance has been changed. */ void EquitiesModel::slotBalanceOrValueChanged(const MyMoneyAccount &account) { if (account.accountType() != eMyMoney::Account::Type::Stock) return; const auto itAcc = d->itemFromId(this, account.id(), Role::EquityID); if (!itAcc) return; d->setAccountBalanceAndValue(itAcc->parent(), itAcc->row(), account, d->m_columns); } auto EquitiesModel::getColumns() { return &d->m_columns; } QString EquitiesModel::getHeaderName(const Column column) { switch(column) { case Equity: return i18n("Equity"); case Symbol: - return i18n("Symbol"); + return i18nc("@title stock symbol column", "Symbol"); case Value: return i18n("Value"); case Quantity: return i18n("Quantity"); case Price: return i18n("Price"); default: return QString(); } } class EquitiesFilterProxyModel::Private { public: Private() : m_mdlColumns(nullptr), m_file(MyMoneyFile::instance()), m_hideClosedAccounts(false), m_hideZeroBalanceAccounts(false) {} ~Private() {} QList *m_mdlColumns; QList m_visColumns; MyMoneyFile *m_file; bool m_hideClosedAccounts; bool m_hideZeroBalanceAccounts; }; #if QT_VERSION < QT_VERSION_CHECK(5,10,0) #define QSortFilterProxyModel KRecursiveFilterProxyModel #endif EquitiesFilterProxyModel::EquitiesFilterProxyModel(QObject *parent, EquitiesModel *model, const QList &columns) : QSortFilterProxyModel(parent), d(new Private) { setRecursiveFilteringEnabled(true); setDynamicSortFilter(true); setFilterKeyColumn(-1); setSortLocaleAware(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSourceModel(model); d->m_mdlColumns = model->getColumns(); d->m_visColumns.append(columns); } #undef QSortFilterProxyModel EquitiesFilterProxyModel::~EquitiesFilterProxyModel() { delete d; } /** * Set if closed accounts should be hidden or not. * @param hideClosedAccounts */ void EquitiesFilterProxyModel::setHideClosedAccounts(const bool hideClosedAccounts) { d->m_hideClosedAccounts = hideClosedAccounts; } /** * Set if zero balance accounts should be hidden or not. * @param hideZeroBalanceAccounts */ void EquitiesFilterProxyModel::setHideZeroBalanceAccounts(const bool hideZeroBalanceAccounts) { d->m_hideZeroBalanceAccounts = hideZeroBalanceAccounts; } bool EquitiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const { Q_UNUSED(source_parent) if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column))) return true; return false; } bool EquitiesFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (d->m_hideClosedAccounts || d->m_hideZeroBalanceAccounts) { const auto ixRow = sourceModel()->index(source_row, EquitiesModel::Equity, source_parent); const auto idAcc = sourceModel()->data(ixRow, EquitiesModel::EquityID).toString(); const auto acc = d->m_file->account(idAcc); if (d->m_hideClosedAccounts && acc.isClosed()) return false; if (d->m_hideZeroBalanceAccounts && acc.accountType() != eMyMoney::Account::Type::Investment && acc.balance().isZero()) // we should never hide investment account because all underlaying stocks will be hidden as well return false; } return true; } QList &EquitiesFilterProxyModel::getVisibleColumns() { return d->m_visColumns; } void EquitiesFilterProxyModel::slotColumnsMenu(const QPoint) { // construct all hideable columns list const QList idColumns { EquitiesModel::Symbol, EquitiesModel::Value, EquitiesModel::Quantity, EquitiesModel::Price }; // create menu QMenu menu(i18n("Displayed columns")); QList actions; foreach (const auto idColumn, idColumns) { auto a = new QAction(nullptr); a->setObjectName(QString::number(idColumn)); a->setText(EquitiesModel::getHeaderName(idColumn)); a->setCheckable(true); a->setChecked(d->m_visColumns.contains(idColumn)); actions.append(a); } menu.addActions(actions); // execute menu and get result const auto retAction = menu.exec(QCursor::pos()); if (retAction) { const auto idColumn = static_cast(retAction->objectName().toInt()); const auto isChecked = retAction->isChecked(); const auto contains = d->m_visColumns.contains(idColumn); if (isChecked && !contains) { // column has just been enabled d->m_visColumns.append(idColumn); // change filtering variable emit columnToggled(idColumn, true); // emit signal for method to add column to model invalidate(); // refresh model to reflect recent changes } else if (!isChecked && contains) { // column has just been disabled d->m_visColumns.removeOne(idColumn); emit columnToggled(idColumn, false); invalidate(); } } } diff --git a/kmymoney/models/securitiesmodel.cpp b/kmymoney/models/securitiesmodel.cpp index c834ab4b1..c882e2ea0 100644 --- a/kmymoney/models/securitiesmodel.cpp +++ b/kmymoney/models/securitiesmodel.cpp @@ -1,378 +1,378 @@ /* * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "securitiesmodel.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyenums.h" class SecuritiesModel::Private { public: Private() : m_file(MyMoneyFile::instance()), m_ndCurrencies(nullptr), m_ndSecurities(nullptr) { QVector columns { Column::Security, Column::Symbol, Column::Type, Column::Market, Column::Currency, Column::Fraction }; foreach (auto const column, columns) m_columns.append(column); } ~Private() {} void loadSecurity(QStandardItem *node, const MyMoneySecurity &sec) { auto itSec = new QStandardItem(sec.name()); node->appendRow(itSec); itSec->setEditable(false); setSecurityData(node, itSec->row(), sec, m_columns); } void setSecurityData(QStandardItem *node, const int row, const MyMoneySecurity &security, const QList &columns) { QStandardItem *cell; auto getCell = [&, row](const auto column) { cell = node->child(row, column); // try to get QStandardItem if (!cell) { // it may be uninitialized cell = new QStandardItem; // so create one node->setChild(row, column, cell); // and add it under the node cell->setEditable(false); // and don't forget that it's non-editable } }; auto colNum = m_columns.indexOf(Column::Security); if (colNum == -1) return; // Security getCell(colNum); if (columns.contains(Column::Security)) { cell->setData(security.name(), Qt::DisplayRole); cell->setData(security.id(), Qt::UserRole); } // Symbol if (columns.contains(Column::Symbol)) { colNum = m_columns.indexOf(Column::Symbol); if (colNum != -1) { getCell(colNum); cell->setData(security.tradingSymbol(), Qt::DisplayRole); } } // Type if (columns.contains(Column::Type)) { colNum = m_columns.indexOf(Column::Type); if (colNum != -1) { getCell(colNum); cell->setData(security.securityTypeToString(security.securityType()), Qt::DisplayRole); } } // Market if (columns.contains(Column::Market)) { colNum = m_columns.indexOf(Column::Market); if (colNum != -1) { getCell(colNum); QString market; if (security.isCurrency()) market = QLatin1String("ISO 4217"); else market = security.tradingMarket(); cell->setData(market, Qt::DisplayRole); } } // Currency if (columns.contains(Column::Currency)) { colNum = m_columns.indexOf(Column::Currency); if (colNum != -1) { getCell(colNum); MyMoneySecurity tradingCurrency; if (!security.isCurrency()) tradingCurrency = m_file->security(security.tradingCurrency()); cell->setData(tradingCurrency.tradingSymbol(), Qt::DisplayRole); } } // Fraction if (columns.contains(Column::Fraction)) { colNum = m_columns.indexOf(Column::Fraction); if (colNum != -1) { getCell(colNum); cell->setData(QString::number(security.smallestAccountFraction()), Qt::DisplayRole); } } } QStandardItem *itemFromSecurityId(QStandardItemModel *model, const QString &securityId) { const auto itemList = model->match(model->index(0, 0), Qt::UserRole, QVariant(securityId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); if (!itemList.isEmpty()) return model->itemFromIndex(itemList.first()); return nullptr; } MyMoneyFile *m_file; QList m_columns; QStandardItem *m_ndCurrencies; QStandardItem *m_ndSecurities; }; SecuritiesModel::SecuritiesModel(QObject *parent) : QStandardItemModel(parent), d(new Private) { init(); } SecuritiesModel::~SecuritiesModel() { delete d; } void SecuritiesModel::init() { QStringList headerLabels; foreach (const auto column, d->m_columns) headerLabels.append(getHeaderName(column)); setHorizontalHeaderLabels(headerLabels); } void SecuritiesModel::load() { this->blockSignals(true); auto rootItem = invisibleRootItem(); auto secList = d->m_file->securityList(); // get all available securities d->m_ndSecurities = new QStandardItem(QStringLiteral("Securities")); d->m_ndSecurities->setEditable(false); rootItem->appendRow(d->m_ndSecurities); foreach (const auto sec, secList) d->loadSecurity(d->m_ndSecurities, sec); secList = d->m_file->currencyList(); // get all available currencies d->m_ndCurrencies = new QStandardItem(QStringLiteral("Currencies")); d->m_ndCurrencies->setEditable(false); rootItem->appendRow(d->m_ndCurrencies); foreach (const auto sec, secList) d->loadSecurity(d->m_ndCurrencies, sec); this->blockSignals(false); } /** * Notify the model that an object has been added. An action is performed only if the object is a security. * */ void SecuritiesModel::slotObjectAdded(eMyMoney::File::Object objType, const QString& id) { // check whether change is about security if (objType != eMyMoney::File::Object::Security) return; // check that we're about to add security auto sec = MyMoneyFile::instance()->security(id); auto itSec = d->itemFromSecurityId(this, id); QStandardItem *node; if (sec.isCurrency()) node = d->m_ndCurrencies; else node = d->m_ndSecurities; // if security doesn't exist in model then add it if (!itSec) { itSec = new QStandardItem(sec.name()); node->appendRow(itSec); itSec->setEditable(false); } d->setSecurityData(node, itSec->row(), sec, d->m_columns); } /** * Notify the model that an object has been modified. An action is performed only if the object is a security. * */ void SecuritiesModel::slotObjectModified(eMyMoney::File::Object objType, const QString& id) { if (objType != eMyMoney::File::Object::Security) return; // check that we're about to modify security auto sec = MyMoneyFile::instance()->security(id); auto itSec = d->itemFromSecurityId(this, id); QStandardItem *node; if (sec.isCurrency()) node = d->m_ndCurrencies; else node = d->m_ndSecurities; d->setSecurityData(node, itSec->row(), sec, d->m_columns); } /** * Notify the model that an object has been removed. An action is performed only if the object is an account. * */ void SecuritiesModel::slotObjectRemoved(eMyMoney::File::Object objType, const QString& id) { if (objType != eMyMoney::File::Object::Security) return; const auto indexList = match(index(0, 0), Qt::UserRole, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); foreach (const auto index, indexList) removeRow(index.row(), index.parent()); } auto SecuritiesModel::getColumns() { return &d->m_columns; } QString SecuritiesModel::getHeaderName(const Column column) { switch(column) { case Security: return i18n("Security"); case Symbol: - return i18n("Symbol"); + return i18nc("@title stock symbol column", "Symbol"); case Type: return i18n("Type"); case Market: return i18n("Market"); case Currency: return i18n("Currency"); case Fraction: return i18n("Fraction"); default: return QString(); } } class SecuritiesFilterProxyModel::Private { public: Private() : m_mdlColumns(nullptr), m_file(MyMoneyFile::instance()) {} ~Private() {} QList *m_mdlColumns; QList m_visColumns; MyMoneyFile *m_file; }; #if QT_VERSION < QT_VERSION_CHECK(5,10,0) #define QSortFilterProxyModel KRecursiveFilterProxyModel #endif SecuritiesFilterProxyModel::SecuritiesFilterProxyModel(QObject *parent, SecuritiesModel *model, const QList &columns) : QSortFilterProxyModel(parent), d(new Private) { setRecursiveFilteringEnabled(true); setDynamicSortFilter(true); setFilterKeyColumn(-1); setSortLocaleAware(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSourceModel(model); d->m_mdlColumns = model->getColumns(); d->m_visColumns.append(columns); } #undef QSortFilterProxyModel SecuritiesFilterProxyModel::~SecuritiesFilterProxyModel() { delete d; } bool SecuritiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const { Q_UNUSED(source_parent) if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column))) return true; return false; } QList &SecuritiesFilterProxyModel::getVisibleColumns() { return d->m_visColumns; } void SecuritiesFilterProxyModel::slotColumnsMenu(const QPoint) { // construct all hideable columns list const QList idColumns { SecuritiesModel::Symbol, SecuritiesModel::Type, SecuritiesModel::Market, SecuritiesModel::Currency, SecuritiesModel::Fraction }; // create menu QMenu menu(i18n("Displayed columns")); QList actions; foreach (const auto idColumn, idColumns) { auto a = new QAction(nullptr); a->setObjectName(QString::number(idColumn)); a->setText(SecuritiesModel::getHeaderName(idColumn)); a->setCheckable(true); a->setChecked(d->m_visColumns.contains(idColumn)); actions.append(a); } menu.addActions(actions); // execute menu and get result const auto retAction = menu.exec(QCursor::pos()); if (retAction) { const auto idColumn = static_cast(retAction->objectName().toInt()); const auto isChecked = retAction->isChecked(); const auto contains = d->m_visColumns.contains(idColumn); if (isChecked && !contains) { // column has just been enabled d->m_visColumns.append(idColumn); // change filtering variable emit columnToggled(idColumn, true); // emit signal for method to add column to model invalidate(); // refresh model to reflect recent changes } else if (!isChecked && contains) { // column has just been disabled d->m_visColumns.removeOne(idColumn); emit columnToggled(idColumn, false); invalidate(); } } } diff --git a/kmymoney/mymoney/mymoneyfile.cpp b/kmymoney/mymoney/mymoneyfile.cpp index ded0ddec5..67e2db74e 100644 --- a/kmymoney/mymoney/mymoneyfile.cpp +++ b/kmymoney/mymoney/mymoneyfile.cpp @@ -1,3582 +1,3582 @@ /* * Copyright 2000-2003 Michael Edwardes * Copyright 2001-2002 Felix Rodriguez * Copyright 2002-2004 Kevin Tambascio * Copyright 2004-2005 Ace Jones * Copyright 2006-2019 Thomas Baumgart * Copyright 2006 Darren Gould * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mymoneyfile.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystoragemgr.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneyaccountloan.h" #include "mymoneysecurity.h" #include "mymoneyreport.h" #include "mymoneybalancecache.h" #include "mymoneybudget.h" #include "mymoneyprice.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneycostcenter.h" #include "mymoneyexception.h" #include "onlinejob.h" #include "storageenums.h" #include "mymoneyenums.h" // include the following line to get a 'cout' for debug purposes // #include using namespace eMyMoney; const QString MyMoneyFile::AccountSeparator = QChar(':'); MyMoneyFile MyMoneyFile::file; typedef QList > BalanceNotifyList; typedef QMap CacheNotifyList; /// @todo make this template based class MyMoneyNotification { public: MyMoneyNotification(File::Mode mode, const MyMoneyTransaction& t) : m_objType(File::Object::Transaction), m_notificationMode(mode), m_id(t.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyAccount& acc) : m_objType(File::Object::Account), m_notificationMode(mode), m_id(acc.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyInstitution& institution) : m_objType(File::Object::Institution), m_notificationMode(mode), m_id(institution.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyPayee& payee) : m_objType(File::Object::Payee), m_notificationMode(mode), m_id(payee.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyTag& tag) : m_objType(File::Object::Tag), m_notificationMode(mode), m_id(tag.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneySchedule& schedule) : m_objType(File::Object::Schedule), m_notificationMode(mode), m_id(schedule.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneySecurity& security) : m_objType(File::Object::Security), m_notificationMode(mode), m_id(security.id()) { } MyMoneyNotification(File::Mode mode, const onlineJob& job) : m_objType(File::Object::OnlineJob), m_notificationMode(mode), m_id(job.id()) { } File::Object objectType() const { return m_objType; } File::Mode notificationMode() const { return m_notificationMode; } const QString& id() const { return m_id; } protected: MyMoneyNotification(File::Object obj, File::Mode mode, const QString& id) : m_objType(obj), m_notificationMode(mode), m_id(id) {} private: File::Object m_objType; File::Mode m_notificationMode; QString m_id; }; class MyMoneyFile::Private { public: Private() : m_storage(0), m_inTransaction(false) {} ~Private() { delete m_storage; } /** * This method is used to add an id to the list of objects * to be removed from the cache. If id is empty, then nothing is added to the list. * * @param id id of object to be notified * @param reload reload the object (@c true) or not (@c false). The default is @c true * @see attach, detach */ void addCacheNotification(const QString& id, const QDate& date) { if (!id.isEmpty()) m_balanceNotifyList.append(std::make_pair(id, date)); } /** * This method is used to clear the notification list */ void clearCacheNotification() { // reset list to be empty m_balanceNotifyList.clear(); } /** * This method is used to clear all * objects mentioned in m_notificationList from the cache. */ void notify() { foreach (const BalanceNotifyList::value_type & i, m_balanceNotifyList) { m_balanceChangedSet += i.first; if (i.second.isValid()) { m_balanceCache.clear(i.first, i.second); } else { m_balanceCache.clear(i.first); } } clearCacheNotification(); } /** * This method checks if a storage object is attached and * throws and exception if not. */ inline void checkStorage() const { if (m_storage == 0) throw MYMONEYEXCEPTION_CSTRING("No storage object attached to MyMoneyFile"); } /** * This method checks that a transaction has been started with * startTransaction() and throws an exception otherwise. Calls * checkStorage() to make sure a storage object is present and attached. */ void checkTransaction(const char* txt) const { checkStorage(); if (!m_inTransaction) throw MYMONEYEXCEPTION(QString::fromLatin1("No transaction started for %1").arg(QString::fromLatin1(txt))); } void priceChanged(const MyMoneyFile& file, const MyMoneyPrice price) { // get all affected accounts and add them to the m_valueChangedSet QList accList; file.accountList(accList); QList::const_iterator account_it; for (account_it = accList.constBegin(); account_it != accList.constEnd(); ++account_it) { QString currencyId = account_it->currencyId(); if (currencyId != file.baseCurrency().id() && (currencyId == price.from() || currencyId == price.to())) { // this account is not in the base currency and the price affects it's value m_valueChangedSet.insert(account_it->id()); } } } /** * This member points to the storage strategy */ MyMoneyStorageMgr *m_storage; bool m_inTransaction; MyMoneySecurity m_baseCurrency; /** * @brief Cache for MyMoneyObjects * * It is also used to emit the objectAdded() and objectModified() signals. * => If one of these signals is used, you must use this cache. */ MyMoneyPriceList m_priceCache; MyMoneyBalanceCache m_balanceCache; /** * This member keeps a list of account ids to notify * after a single operation is completed. The balance cache * is cleared for that account and all dates on or after * the one supplied. If the date is invalid, the entire * balance cache is cleared for that account. */ BalanceNotifyList m_balanceNotifyList; /** * This member keeps a list of account ids for which * a balanceChanged() signal needs to be emitted when * a set of operations has been committed. * * @sa MyMoneyFile::commitTransaction() */ QSet m_balanceChangedSet; /** * This member keeps a list of account ids for which * a valueChanged() signal needs to be emitted when * a set of operations has been committed. * * @sa MyMoneyFile::commitTransaction() */ QSet m_valueChangedSet; /** * This member keeps the list of changes in the engine * in historical order. The type can be 'added', 'modified' * or removed. */ QList m_changeSet; }; class MyMoneyNotifier { public: MyMoneyNotifier(MyMoneyFile::Private* file) { m_file = file; m_file->clearCacheNotification(); } ~MyMoneyNotifier() { m_file->notify(); } private: MyMoneyFile::Private* m_file; }; MyMoneyFile::MyMoneyFile() : d(new Private) { } MyMoneyFile::~MyMoneyFile() { delete d; } MyMoneyFile::MyMoneyFile(MyMoneyStorageMgr *storage) : d(new Private) { attachStorage(storage); } MyMoneyFile* MyMoneyFile::instance() { return &file; } void MyMoneyFile::attachStorage(MyMoneyStorageMgr* const storage) { if (d->m_storage != 0) throw MYMONEYEXCEPTION_CSTRING("Storage already attached"); if (storage == 0) throw MYMONEYEXCEPTION_CSTRING("Storage must not be 0"); d->m_storage = storage; // force reload of base currency d->m_baseCurrency = MyMoneySecurity(); // and the whole cache d->m_balanceCache.clear(); d->m_priceCache.clear(); // notify application about new data availability emit beginChangeNotification(); emit dataChanged(); emit endChangeNotification(); } void MyMoneyFile::detachStorage(MyMoneyStorageMgr* const /* storage */) { d->m_balanceCache.clear(); d->m_priceCache.clear(); d->m_storage = nullptr; } MyMoneyStorageMgr* MyMoneyFile::storage() const { return d->m_storage; } bool MyMoneyFile::storageAttached() const { return d->m_storage != 0; } void MyMoneyFile::startTransaction() { d->checkStorage(); if (d->m_inTransaction) { throw MYMONEYEXCEPTION_CSTRING("Already started a transaction!"); } d->m_storage->startTransaction(); d->m_inTransaction = true; d->m_changeSet.clear(); } bool MyMoneyFile::hasTransaction() const { return d->m_inTransaction; } void MyMoneyFile::commitTransaction() { d->checkTransaction(Q_FUNC_INFO); // commit the transaction in the storage const auto changed = d->m_storage->commitTransaction(); d->m_inTransaction = false; // collect notifications about removed objects QStringList removedObjects; const auto& set = d->m_changeSet; for (const auto& change : set) { switch (change.notificationMode()) { case File::Mode::Remove: removedObjects += change.id(); break; default: break; } } // inform the outside world about the beginning of notifications emit beginChangeNotification(); // Now it's time to send out some signals to the outside world // First we go through the d->m_changeSet and emit respective // signals about addition, modification and removal of engine objects const auto& changes = d->m_changeSet; for (const auto& change : changes) { switch (change.notificationMode()) { case File::Mode::Remove: emit objectRemoved(change.objectType(), change.id()); // if there is a balance change recorded for this account remove it since the account itself will be removed // this can happen when deleting categories that have transactions and the reassign category feature was used d->m_balanceChangedSet.remove(change.id()); break; case File::Mode::Add: if (!removedObjects.contains(change.id())) { emit objectAdded(change.objectType(), change.id()); } break; case File::Mode::Modify: if (!removedObjects.contains(change.id())) { emit objectModified(change.objectType(), change.id()); } break; } } // we're done with the change set, so we clear it d->m_changeSet.clear(); // now send out the balanceChanged signal for all those // accounts for which we have an indication about a possible // change. const auto& balanceChanges = d->m_balanceChangedSet; for (const auto& id : balanceChanges) { if (!removedObjects.contains(id)) { // if we notify about balance change we don't need to notify about value change // for the same account since a balance change implies a value change d->m_valueChangedSet.remove(id); emit balanceChanged(account(id)); } } d->m_balanceChangedSet.clear(); // now notify about the remaining value changes const auto& m_valueChanges = d->m_valueChangedSet; for (const auto& id : m_valueChanges) { if (!removedObjects.contains(id)) { emit valueChanged(account(id)); } } d->m_valueChangedSet.clear(); // as a last action, send out the global dataChanged signal if (changed) emit dataChanged(); // inform the outside world about the end of notifications emit endChangeNotification(); } void MyMoneyFile::rollbackTransaction() { d->checkTransaction(Q_FUNC_INFO); d->m_storage->rollbackTransaction(); d->m_inTransaction = false; d->m_balanceChangedSet.clear(); d->m_valueChangedSet.clear(); d->m_changeSet.clear(); } void MyMoneyFile::addInstitution(MyMoneyInstitution& institution) { // perform some checks to see that the institution stuff is OK. For // now we assume that the institution must have a name, the ID is not set // and it does not have a parent (MyMoneyFile). if (institution.name().length() == 0 || institution.id().length() != 0) throw MYMONEYEXCEPTION_CSTRING("Not a new institution"); d->checkTransaction(Q_FUNC_INFO); d->m_storage->addInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Add, institution); } void MyMoneyFile::modifyInstitution(const MyMoneyInstitution& institution) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifyInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); } void MyMoneyFile::modifyTransaction(const MyMoneyTransaction& transaction) { d->checkTransaction(Q_FUNC_INFO); MyMoneyTransaction tCopy(transaction); // now check the splits bool loanAccountAffected = false; const auto splits1 = transaction.splits(); for (const auto& split : splits1) { // the following line will throw an exception if the // account does not exist auto acc = MyMoneyFile::account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION_CSTRING("Cannot store split with no account assigned"); if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION_CSTRING("Cannot store split referencing standard account"); if (acc.isLoan() && (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))) loanAccountAffected = true; } // change transfer splits between asset/liability and loan accounts // into amortization splits if (loanAccountAffected) { const auto splits = transaction.splits(); for (const auto& split : splits) { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) { auto acc = MyMoneyFile::account(split.accountId()); if (acc.isAssetLiability()) { MyMoneySplit s = split; s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); tCopy.modifySplit(s); } } } } // clear all changed objects from cache MyMoneyNotifier notifier(d); // get the current setting of this transaction MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id()); // scan the splits again to update notification list // and mark all accounts that are referenced const auto splits2 = tr.splits(); foreach (const auto& split, splits2) d->addCacheNotification(split.accountId(), tr.postDate()); // make sure the value is rounded to the accounts precision fixSplitPrecision(tCopy); // perform modification d->m_storage->modifyTransaction(tCopy); // and mark all accounts that are referenced const auto splits3 = tCopy.splits(); for (const auto& split : splits3) d->addCacheNotification(split.accountId(), tCopy.postDate()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, transaction); } void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount account(_account); auto acc = MyMoneyFile::account(account.id()); // check that for standard accounts only specific parameters are changed if (isStandardAccount(account.id())) { // make sure to use the stuff we found on file account = acc; // and only use the changes that are allowed account.setName(_account.name()); account.setCurrencyId(_account.currencyId()); // now check that it is the same if (!(account == _account)) throw MYMONEYEXCEPTION_CSTRING("Unable to modify the standard account groups"); } if (account.accountType() != acc.accountType() && !account.isLiquidAsset() && !acc.isLiquidAsset()) throw MYMONEYEXCEPTION_CSTRING("Unable to change account type"); // if the account was moved to another institution, we notify // the old one as well as the new one and the structure change if (acc.institutionId() != account.institutionId()) { MyMoneyInstitution inst; if (!acc.institutionId().isEmpty()) { inst = institution(acc.institutionId()); inst.removeAccountId(acc.id()); modifyInstitution(inst); // modifyInstitution updates d->m_changeSet already } if (!account.institutionId().isEmpty()) { inst = institution(account.institutionId()); inst.addAccountId(acc.id()); modifyInstitution(inst); // modifyInstitution updates d->m_changeSet already } } d->m_storage->modifyAccount(account); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, account); } void MyMoneyFile::reparentAccount(MyMoneyAccount &acc, MyMoneyAccount& parent) { d->checkTransaction(Q_FUNC_INFO); // check that it's not one of the standard account groups if (isStandardAccount(acc.id())) throw MYMONEYEXCEPTION_CSTRING("Unable to reparent the standard account groups"); if (acc.accountGroup() == parent.accountGroup() || (acc.accountType() == Account::Type::Income && parent.accountType() == Account::Type::Expense) || (acc.accountType() == Account::Type::Expense && parent.accountType() == Account::Type::Income)) { if (acc.isInvest() && parent.accountType() != Account::Type::Investment) throw MYMONEYEXCEPTION_CSTRING("Unable to reparent Stock to non-investment account"); if (parent.accountType() == Account::Type::Investment && !acc.isInvest()) throw MYMONEYEXCEPTION_CSTRING("Unable to reparent non-stock to investment account"); // keep a notification of the current parent MyMoneyAccount curParent = account(acc.parentAccountId()); d->m_storage->reparentAccount(acc, parent); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, curParent); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } else throw MYMONEYEXCEPTION_CSTRING("Unable to reparent to different account type"); } MyMoneyInstitution MyMoneyFile::institution(const QString& id) const { return d->m_storage->institution(id); } MyMoneyAccount MyMoneyFile::account(const QString& id) const { if (Q_UNLIKELY(id.isEmpty())) // FIXME: Stop requesting accounts with empty id return MyMoneyAccount(); return d->m_storage->account(id); } MyMoneyAccount MyMoneyFile::subAccountByName(const MyMoneyAccount& account, const QString& name) const { static MyMoneyAccount nullAccount; const auto accounts = account.accountList(); for (const auto& acc : accounts) { const auto sacc = MyMoneyFile::account(acc); if (sacc.name().compare(name) == 0) return sacc; } return nullAccount; } MyMoneyAccount MyMoneyFile::accountByName(const QString& name) const { try { return d->m_storage->accountByName(name); } catch (const MyMoneyException &) { } return MyMoneyAccount(); } void MyMoneyFile::removeTransaction(const MyMoneyTransaction& transaction) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // get the engine's idea about this transaction MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id()); // scan the splits again to update notification list const auto splits = tr.splits(); for (const auto& split : splits) { auto acc = account(split.accountId()); if (acc.isClosed()) throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot remove transaction that references a closed account.")); d->addCacheNotification(split.accountId(), tr.postDate()); //FIXME-ALEX Do I need to add d->addCacheNotification(split.tagList()); ?? } d->m_storage->removeTransaction(transaction); // remove a possible notification of that same object from the changeSet QList::iterator it; for(it = d->m_changeSet.begin(); it != d->m_changeSet.end();) { if((*it).id() == transaction.id()) { it = d->m_changeSet.erase(it); } else { ++it; } } d->m_changeSet += MyMoneyNotification(File::Mode::Remove, transaction); } bool MyMoneyFile::hasActiveSplits(const QString& id) const { d->checkStorage(); return d->m_storage->hasActiveSplits(id); } bool MyMoneyFile::isStandardAccount(const QString& id) const { d->checkStorage(); return d->m_storage->isStandardAccount(id); } void MyMoneyFile::setAccountName(const QString& id, const QString& name) const { d->checkTransaction(Q_FUNC_INFO); auto acc = account(id); d->m_storage->setAccountName(id, name); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } void MyMoneyFile::removeAccount(const MyMoneyAccount& account) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount parent; MyMoneyAccount acc; MyMoneyInstitution institution; // check that the account and its parent exist // this will throw an exception if the id is unknown acc = MyMoneyFile::account(account.id()); parent = MyMoneyFile::account(account.parentAccountId()); if (!acc.institutionId().isEmpty()) institution = MyMoneyFile::institution(acc.institutionId()); // check that it's not one of the standard account groups if (isStandardAccount(account.id())) throw MYMONEYEXCEPTION_CSTRING("Unable to remove the standard account groups"); if (hasActiveSplits(account.id())) { throw MYMONEYEXCEPTION_CSTRING("Unable to remove account with active splits"); } // collect all sub-ordinate accounts for notification const auto accounts = acc.accountList(); for (const auto& id : accounts) d->m_changeSet += MyMoneyNotification(File::Mode::Modify, MyMoneyFile::account(id)); // don't forget the parent and a possible institution if (!institution.id().isEmpty()) { institution.removeAccountId(account.id()); d->m_storage->modifyInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); } acc.setInstitutionId(QString()); d->m_storage->removeAccount(acc); d->m_balanceCache.clear(acc.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, acc); } void MyMoneyFile::removeAccountList(const QStringList& account_list, unsigned int level) { if (level > 100) throw MYMONEYEXCEPTION_CSTRING("Too deep recursion in [MyMoneyFile::removeAccountList]!"); d->checkTransaction(Q_FUNC_INFO); // upon entry, we check that we could proceed with the operation if (!level) { if (!hasOnlyUnusedAccounts(account_list, 0)) { throw MYMONEYEXCEPTION_CSTRING("One or more accounts cannot be removed"); } } // process all accounts in the list and test if they have transactions assigned foreach (const auto sAccount, account_list) { auto a = d->m_storage->account(sAccount); //qDebug() << "Deleting account '"<< a.name() << "'"; // first remove all sub-accounts if (!a.accountList().isEmpty()) { removeAccountList(a.accountList(), level + 1); // then remove account itself, but we first have to get // rid of the account list that is still stored in // the MyMoneyAccount object. Easiest way is to get a fresh copy. a = d->m_storage->account(sAccount); } // make sure to remove the item from the cache removeAccount(a); } } bool MyMoneyFile::hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level) { if (level > 100) throw MYMONEYEXCEPTION_CSTRING("Too deep recursion in [MyMoneyFile::hasOnlyUnusedAccounts]!"); // process all accounts in the list and test if they have transactions assigned for (const auto& sAccount : account_list) { if (transactionCount(sAccount) != 0) return false; // the current account has a transaction assigned if (!hasOnlyUnusedAccounts(account(sAccount).accountList(), level + 1)) return false; // some sub-account has a transaction assigned } return true; // all subaccounts unused } void MyMoneyFile::removeInstitution(const MyMoneyInstitution& institution) { d->checkTransaction(Q_FUNC_INFO); MyMoneyInstitution inst = MyMoneyFile::institution(institution.id()); bool blocked = signalsBlocked(); blockSignals(true); const auto accounts = inst.accountList(); for (const auto& acc : accounts) { auto a = account(acc); a.setInstitutionId(QString()); modifyAccount(a); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, a); } blockSignals(blocked); d->m_storage->removeInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, institution); } void MyMoneyFile::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { // make sure we have a currency. If none is assigned, we assume base currency if (newAccount.currencyId().isEmpty()) newAccount.setCurrencyId(baseCurrency().id()); MyMoneyFileTransaction ft; try { int pos; // check for ':' in the name and use it as separator for a hierarchy while ((pos = newAccount.name().indexOf(MyMoneyFile::AccountSeparator)) != -1) { QString part = newAccount.name().left(pos); QString remainder = newAccount.name().mid(pos + 1); const MyMoneyAccount& existingAccount = subAccountByName(parentAccount, part); if (existingAccount.id().isEmpty()) { newAccount.setName(part); addAccount(newAccount, parentAccount); parentAccount = newAccount; } else { parentAccount = existingAccount; } newAccount.setParentAccountId(QString()); // make sure, there's no parent newAccount.clearId(); // and no id set for adding newAccount.removeAccountIds(); // and no sub-account ids newAccount.setName(remainder); } addAccount(newAccount, parentAccount); // in case of a loan account, we add the initial payment if ((newAccount.accountType() == Account::Type::Loan || newAccount.accountType() == Account::Type::AssetLoan) && !newAccount.value("kmm-loan-payment-acc").isEmpty() && !newAccount.value("kmm-loan-payment-date").isEmpty()) { MyMoneyAccountLoan acc(newAccount); MyMoneyTransaction t; MyMoneySplit a, b; a.setAccountId(acc.id()); b.setAccountId(acc.value("kmm-loan-payment-acc")); a.setValue(acc.loanAmount()); if (acc.accountType() == Account::Type::Loan) a.setValue(-a.value()); a.setShares(a.value()); b.setValue(-a.value()); b.setShares(b.value()); a.setMemo(i18n("Loan payout")); b.setMemo(i18n("Loan payout")); t.setPostDate(QDate::fromString(acc.value("kmm-loan-payment-date"), Qt::ISODate)); newAccount.deletePair("kmm-loan-payment-acc"); newAccount.deletePair("kmm-loan-payment-date"); MyMoneyFile::instance()->modifyAccount(newAccount); t.addSplit(a); t.addSplit(b); addTransaction(t); createOpeningBalanceTransaction(newAccount, openingBal); // in case of an investment account we check if we should create // a brokerage account } else if (newAccount.accountType() == Account::Type::Investment && !brokerageAccount.name().isEmpty()) { addAccount(brokerageAccount, parentAccount); // set a link from the investment account to the brokerage account modifyAccount(newAccount); createOpeningBalanceTransaction(brokerageAccount, openingBal); } else createOpeningBalanceTransaction(newAccount, openingBal); ft.commit(); } catch (const MyMoneyException &e) { qWarning("Unable to create account: %s", e.what()); throw; } } void MyMoneyFile::addAccount(MyMoneyAccount& account, MyMoneyAccount& parent) { d->checkTransaction(Q_FUNC_INFO); MyMoneyInstitution institution; // perform some checks to see that the account stuff is OK. For // now we assume that the account must have a name, has no // transaction and sub-accounts and parent account // it's own ID is not set and it does not have a pointer to (MyMoneyFile) if (account.name().length() == 0) throw MYMONEYEXCEPTION_CSTRING("Account has no name"); if (account.id().length() != 0) throw MYMONEYEXCEPTION_CSTRING("New account must have no id"); if (account.accountList().count() != 0) throw MYMONEYEXCEPTION_CSTRING("New account must have no sub-accounts"); if (!account.parentAccountId().isEmpty()) throw MYMONEYEXCEPTION_CSTRING("New account must have no parent-id"); if (account.accountType() == Account::Type::Unknown) throw MYMONEYEXCEPTION_CSTRING("Account has invalid type"); // make sure, that the parent account exists // if not, an exception is thrown. If it exists, // get a copy of the current data auto acc = MyMoneyFile::account(parent.id()); #if 0 // TODO: remove the following code as we now can have multiple accounts // with the same name even in the same hierarchy position of the account tree // // check if the selected name is currently not among the child accounts // if we find one, then return it as the new account QStringList::const_iterator it_a; foreach (const auto accountID, acc.accountList()) { MyMoneyAccount a = MyMoneyFile::account(accountID); if (account.name() == a.name()) { account = a; return; } } #endif // FIXME: make sure, that the parent has the same type // I left it out here because I don't know, if there is // a tight coupling between e.g. checking accounts and the // class asset. It certainly does not make sense to create an // expense account under an income account. Maybe it does, I don't know. // We enforce, that a stock account can never be a parent and // that the parent for a stock account must be an investment. Also, // an investment cannot have another investment account as it's parent if (parent.isInvest()) throw MYMONEYEXCEPTION_CSTRING("Stock account cannot be parent account"); if (account.isInvest() && parent.accountType() != Account::Type::Investment) throw MYMONEYEXCEPTION_CSTRING("Stock account must have investment account as parent "); if (!account.isInvest() && parent.accountType() == Account::Type::Investment) throw MYMONEYEXCEPTION_CSTRING("Investment account can only have stock accounts as children"); // if an institution is set, verify that it exists if (account.institutionId().length() != 0) { // check the presence of the institution. if it // does not exist, an exception is thrown institution = MyMoneyFile::institution(account.institutionId()); } // if we don't have a valid opening date use today if (!account.openingDate().isValid()) { account.setOpeningDate(QDate::currentDate()); } // make sure to set the opening date for categories to a // fixed date (1900-1-1). See #313793 on b.k.o for details if (account.isIncomeExpense()) { account.setOpeningDate(QDate(1900, 1, 1)); } // if we don't have a currency assigned use the base currency if (account.currencyId().isEmpty()) { account.setCurrencyId(baseCurrency().id()); } // make sure the parent id is setup account.setParentAccountId(parent.id()); d->m_storage->addAccount(account); d->m_changeSet += MyMoneyNotification(File::Mode::Add, account); d->m_storage->addAccount(parent, account); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); if (account.institutionId().length() != 0) { institution.addAccountId(account.id()); d->m_storage->modifyInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); } } MyMoneyTransaction MyMoneyFile::createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance) { MyMoneyTransaction t; // if the opening balance is not zero, we need // to create the respective transaction if (!balance.isZero()) { d->checkTransaction(Q_FUNC_INFO); MyMoneySecurity currency = security(acc.currencyId()); MyMoneyAccount openAcc = openingBalanceAccount(currency); if (openAcc.openingDate() > acc.openingDate()) { openAcc.setOpeningDate(acc.openingDate()); modifyAccount(openAcc); } MyMoneySplit s; t.setPostDate(acc.openingDate()); t.setCommodity(acc.currencyId()); s.setAccountId(acc.id()); s.setShares(balance); s.setValue(balance); t.addSplit(s); s.clearId(); s.setAccountId(openAcc.id()); s.setShares(-balance); s.setValue(-balance); t.addSplit(s); addTransaction(t); } return t; } QString MyMoneyFile::openingBalanceTransaction(const MyMoneyAccount& acc) const { QString result; MyMoneySecurity currency = security(acc.currencyId()); MyMoneyAccount openAcc; try { openAcc = openingBalanceAccount(currency); } catch (const MyMoneyException &) { return result; } // Iterate over all the opening balance transactions for this currency MyMoneyTransactionFilter filter; filter.addAccount(openAcc.id()); QList transactions = transactionList(filter); QList::const_iterator it_t = transactions.constBegin(); while (it_t != transactions.constEnd()) { try { // Test whether the transaction also includes a split into // this account (*it_t).splitByAccount(acc.id(), true /*match*/); // If so, we have a winner! result = (*it_t).id(); break; } catch (const MyMoneyException &) { // If not, keep searching ++it_t; } } return result; } MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) { if (!security.isCurrency()) throw MYMONEYEXCEPTION_CSTRING("Opening balance for non currencies not supported"); try { return openingBalanceAccount_internal(security); } catch (const MyMoneyException &) { MyMoneyFileTransaction ft; MyMoneyAccount acc; try { acc = createOpeningBalanceAccount(security); ft.commit(); } catch (const MyMoneyException &) { qDebug("Unable to create opening balance account for security %s", qPrintable(security.id())); } return acc; } } MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const { return openingBalanceAccount_internal(security); } MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const { if (!security.isCurrency()) throw MYMONEYEXCEPTION_CSTRING("Opening balance for non currencies not supported"); MyMoneyAccount acc; QList accounts; QList::ConstIterator it; accountList(accounts, equity().accountList(), true); for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { if (it->value("OpeningBalanceAccount") == QLatin1String("Yes") && it->currencyId() == security.id()) { acc = *it; break; } } if (acc.id().isEmpty()) { for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { if (it->name().startsWith(MyMoneyFile::openingBalancesPrefix()) && it->currencyId() == security.id()) { acc = *it; break; } } } if (acc.id().isEmpty()) throw MYMONEYEXCEPTION(QString::fromLatin1("No opening balance account for %1").arg(security.tradingSymbol())); return acc; } MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount acc; QList accounts; QList::ConstIterator it; accountList(accounts, equity().accountList(), true); // find present opening balance accounts without containing '(' QString name; QString parentAccountId; QRegExp exp(QString("\\([A-Z]{3}\\)")); for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { if (it->value("OpeningBalanceAccount") == QLatin1String("Yes") && exp.indexIn(it->name()) == -1) { name = it->name(); parentAccountId = it->parentAccountId(); break; } } if (name.isEmpty()) name = MyMoneyFile::openingBalancesPrefix(); if (security.id() != baseCurrency().id()) { name += QString(" (%1)").arg(security.id()); } acc.setName(name); acc.setAccountType(Account::Type::Equity); acc.setCurrencyId(security.id()); acc.setValue("OpeningBalanceAccount", "Yes"); MyMoneyAccount parent = !parentAccountId.isEmpty() ? account(parentAccountId) : equity(); this->addAccount(acc, parent); return acc; } void MyMoneyFile::addTransaction(MyMoneyTransaction& transaction) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // perform some checks to see that the transaction stuff is OK. For // now we assume that // * no ids are assigned // * the date valid (must not be empty) // * the referenced accounts in the splits exist // first perform all the checks if (!transaction.id().isEmpty()) throw MYMONEYEXCEPTION_CSTRING("Unable to add transaction with id set"); if (!transaction.postDate().isValid()) throw MYMONEYEXCEPTION_CSTRING("Unable to add transaction with invalid postdate"); // now check the splits auto loanAccountAffected = false; const auto splits1 = transaction.splits(); for (const auto& split : splits1) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts auto acc = MyMoneyFile::account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION_CSTRING("Cannot add split with no account assigned"); if (acc.isLoan()) loanAccountAffected = true; if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION_CSTRING("Cannot add split referencing standard account"); } // change transfer splits between asset/liability and loan accounts // into amortization splits if (loanAccountAffected) { foreach (const auto split, transaction.splits()) { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) { auto acc = MyMoneyFile::account(split.accountId()); if (acc.isAssetLiability()) { MyMoneySplit s = split; s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); transaction.modifySplit(s); } } } } // check that we have a commodity if (transaction.commodity().isEmpty()) { transaction.setCommodity(baseCurrency().id()); } // make sure the value is rounded to the accounts precision fixSplitPrecision(transaction); // then add the transaction to the file global pool d->m_storage->addTransaction(transaction); // scan the splits again to update notification list const auto splits2 = transaction.splits(); for (const auto& split : splits2) d->addCacheNotification(split.accountId(), transaction.postDate()); d->m_changeSet += MyMoneyNotification(File::Mode::Add, transaction); } MyMoneyTransaction MyMoneyFile::transaction(const QString& id) const { d->checkStorage(); return d->m_storage->transaction(id); } MyMoneyTransaction MyMoneyFile::transaction(const QString& account, const int idx) const { d->checkStorage(); return d->m_storage->transaction(account, idx); } void MyMoneyFile::addPayee(MyMoneyPayee& payee) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->addPayee(payee); d->m_changeSet += MyMoneyNotification(File::Mode::Add, payee); } MyMoneyPayee MyMoneyFile::payee(const QString& id) const { if (Q_UNLIKELY(id.isEmpty())) return MyMoneyPayee(); return d->m_storage->payee(id); } MyMoneyPayee MyMoneyFile::payeeByName(const QString& name) const { d->checkStorage(); return d->m_storage->payeeByName(name); } void MyMoneyFile::modifyPayee(const MyMoneyPayee& payee) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifyPayee(payee); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, payee); } void MyMoneyFile::removePayee(const MyMoneyPayee& payee) { d->checkTransaction(Q_FUNC_INFO); // FIXME we need to make sure, that the payee is not referenced anymore d->m_storage->removePayee(payee); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, payee); } void MyMoneyFile::addTag(MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->addTag(tag); d->m_changeSet += MyMoneyNotification(File::Mode::Add, tag); } MyMoneyTag MyMoneyFile::tag(const QString& id) const { return d->m_storage->tag(id); } MyMoneyTag MyMoneyFile::tagByName(const QString& name) const { d->checkStorage(); return d->m_storage->tagByName(name); } void MyMoneyFile::modifyTag(const MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifyTag(tag); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, tag); } void MyMoneyFile::removeTag(const MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); // FIXME we need to make sure, that the tag is not referenced anymore d->m_storage->removeTag(tag); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, tag); } void MyMoneyFile::accountList(QList& list, const QStringList& idlist, const bool recursive) const { d->checkStorage(); if (idlist.isEmpty()) { d->m_storage->accountList(list); #if 0 // TODO: I have no idea what this was good for, but it caused the networth report // to show double the numbers so I commented it out (ipwizard, 2008-05-24) if (d->m_storage && (list.isEmpty() || list.size() != d->m_storage->accountCount())) { d->m_storage->accountList(list); d->m_cache.preloadAccount(list); } #endif QList::Iterator it; for (it = list.begin(); it != list.end();) { if (isStandardAccount((*it).id())) { it = list.erase(it); } else { ++it; } } } else { QList::ConstIterator it; QList list_a; d->m_storage->accountList(list_a); for (it = list_a.constBegin(); it != list_a.constEnd(); ++it) { if (!isStandardAccount((*it).id())) { if (idlist.indexOf((*it).id()) != -1) { list.append(*it); if (recursive == true && !(*it).accountList().isEmpty()) { accountList(list, (*it).accountList(), true); } } } } } } QList MyMoneyFile::institutionList() const { return d->m_storage->institutionList(); } // general get functions MyMoneyPayee MyMoneyFile::user() const { d->checkStorage(); return d->m_storage->user(); } // general set functions void MyMoneyFile::setUser(const MyMoneyPayee& user) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->setUser(user); } bool MyMoneyFile::dirty() const { if (!d->m_storage) return false; return d->m_storage->dirty(); } void MyMoneyFile::setDirty() const { d->checkStorage(); d->m_storage->setDirty(); } unsigned int MyMoneyFile::accountCount() const { d->checkStorage(); return d->m_storage->accountCount(); } void MyMoneyFile::ensureDefaultCurrency(MyMoneyAccount& acc) const { if (acc.currencyId().isEmpty()) { if (!baseCurrency().id().isEmpty()) acc.setCurrencyId(baseCurrency().id()); } } MyMoneyAccount MyMoneyFile::liability() const { d->checkStorage(); return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Liability)); } MyMoneyAccount MyMoneyFile::asset() const { d->checkStorage(); return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Asset)); } MyMoneyAccount MyMoneyFile::expense() const { d->checkStorage(); return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Expense)); } MyMoneyAccount MyMoneyFile::income() const { d->checkStorage(); return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Income)); } MyMoneyAccount MyMoneyFile::equity() const { d->checkStorage(); return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Equity)); } unsigned int MyMoneyFile::transactionCount(const QString& account) const { d->checkStorage(); return d->m_storage->transactionCount(account); } unsigned int MyMoneyFile::transactionCount() const { return transactionCount(QString()); } QMap MyMoneyFile::transactionCountMap() const { d->checkStorage(); return d->m_storage->transactionCountMap(); } unsigned int MyMoneyFile::institutionCount() const { d->checkStorage(); return d->m_storage->institutionCount(); } MyMoneyMoney MyMoneyFile::balance(const QString& id, const QDate& date) const { if (date.isValid()) { MyMoneyBalanceCacheItem bal = d->m_balanceCache.balance(id, date); if (bal.isValid()) return bal.balance(); } d->checkStorage(); MyMoneyMoney returnValue = d->m_storage->balance(id, date); if (date.isValid()) { d->m_balanceCache.insert(id, date, returnValue); } return returnValue; } MyMoneyMoney MyMoneyFile::balance(const QString& id) const { return balance(id, QDate()); } MyMoneyMoney MyMoneyFile::clearedBalance(const QString &id, const QDate& date) const { MyMoneyMoney cleared; QList list; cleared = balance(id, date); MyMoneyAccount account = this->account(id); MyMoneyMoney factor(1, 1); if (account.accountGroup() == Account::Type::Liability || account.accountGroup() == Account::Type::Equity) factor = -factor; MyMoneyTransactionFilter filter; filter.addAccount(id); filter.setDateFilter(QDate(), date); filter.setReportAllSplits(false); filter.addState((int)TransactionFilter::State::NotReconciled); transactionList(list, filter); for (QList::const_iterator it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { const QList& splits = (*it_t).splits(); for (QList::const_iterator it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { const MyMoneySplit &split = (*it_s); if (split.accountId() != id) continue; cleared -= split.shares(); } } return cleared * factor; } MyMoneyMoney MyMoneyFile::totalBalance(const QString& id, const QDate& date) const { d->checkStorage(); return d->m_storage->totalBalance(id, date); } MyMoneyMoney MyMoneyFile::totalBalance(const QString& id) const { return totalBalance(id, QDate()); } void MyMoneyFile::warningMissingRate(const QString& fromId, const QString& toId) const { MyMoneySecurity from, to; try { from = security(fromId); to = security(toId); qWarning("Missing price info for conversion from %s to %s", qPrintable(from.name()), qPrintable(to.name())); } catch (const MyMoneyException &e) { qWarning("Missing security caught in MyMoneyFile::warningMissingRate(). %s", e.what()); } } void MyMoneyFile::transactionList(QList >& list, MyMoneyTransactionFilter& filter) const { d->checkStorage(); d->m_storage->transactionList(list, filter); } void MyMoneyFile::transactionList(QList& list, MyMoneyTransactionFilter& filter) const { d->checkStorage(); d->m_storage->transactionList(list, filter); } QList MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const { d->checkStorage(); return d->m_storage->transactionList(filter); } QList MyMoneyFile::payeeList() const { return d->m_storage->payeeList(); } QList MyMoneyFile::tagList() const { return d->m_storage->tagList(); } QString MyMoneyFile::accountToCategory(const QString& accountId, bool includeStandardAccounts) const { MyMoneyAccount acc; QString rc; if (!accountId.isEmpty()) { acc = account(accountId); do { if (!rc.isEmpty()) rc = AccountSeparator + rc; rc = acc.name() + rc; acc = account(acc.parentAccountId()); } while (!acc.id().isEmpty() && (includeStandardAccounts || !isStandardAccount(acc.id()))); } return rc; } QString MyMoneyFile::categoryToAccount(const QString& category, Account::Type type) const { QString id; // search the category in the expense accounts and if it is not found, try // to locate it in the income accounts if (type == Account::Type::Unknown || type == Account::Type::Expense) { id = locateSubAccount(MyMoneyFile::instance()->expense(), category); } if ((id.isEmpty() && type == Account::Type::Unknown) || type == Account::Type::Income) { id = locateSubAccount(MyMoneyFile::instance()->income(), category); } return id; } QString MyMoneyFile::categoryToAccount(const QString& category) const { return categoryToAccount(category, Account::Type::Unknown); } QString MyMoneyFile::nameToAccount(const QString& name) const { QString id; // search the category in the asset accounts and if it is not found, try // to locate it in the liability accounts id = locateSubAccount(MyMoneyFile::instance()->asset(), name); if (id.isEmpty()) id = locateSubAccount(MyMoneyFile::instance()->liability(), name); return id; } QString MyMoneyFile::parentName(const QString& name) const { return name.section(AccountSeparator, 0, -2); } QString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const QString& category) const { MyMoneyAccount nextBase; QString level, remainder; level = category.section(AccountSeparator, 0, 0); remainder = category.section(AccountSeparator, 1); foreach (const auto sAccount, base.accountList()) { nextBase = account(sAccount); if (nextBase.name() == level) { if (remainder.isEmpty()) { return nextBase.id(); } return locateSubAccount(nextBase, remainder); } } return QString(); } QString MyMoneyFile::value(const QString& key) const { d->checkStorage(); return d->m_storage->value(key); } void MyMoneyFile::setValue(const QString& key, const QString& val) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->setValue(key, val); } void MyMoneyFile::deletePair(const QString& key) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->deletePair(key); } void MyMoneyFile::addSchedule(MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); const auto splits = sched.transaction().splits(); for (const auto& split : splits) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts const auto acc = account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION_CSTRING("Cannot add split with no account assigned"); if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION_CSTRING("Cannot add split referencing standard account"); } d->m_storage->addSchedule(sched); d->m_changeSet += MyMoneyNotification(File::Mode::Add, sched); } void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); foreach (const auto split, sched.transaction().splits()) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts auto acc = MyMoneyFile::account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION_CSTRING("Cannot store split with no account assigned"); if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION_CSTRING("Cannot store split referencing standard account"); } d->m_storage->modifySchedule(sched); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, sched); } void MyMoneyFile::removeSchedule(const MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->removeSchedule(sched); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, sched); } MyMoneySchedule MyMoneyFile::schedule(const QString& id) const { return d->m_storage->schedule(id); } QList MyMoneyFile::scheduleList( const QString& accountId, const Schedule::Type type, const Schedule::Occurrence occurrence, const Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, const bool overdue) const { d->checkStorage(); return d->m_storage->scheduleList(accountId, type, occurrence, paymentType, startDate, endDate, overdue); } QList MyMoneyFile::scheduleList( const QString& accountId) const { return scheduleList(accountId, Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); } QList MyMoneyFile::scheduleList() const { return scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); } QStringList MyMoneyFile::consistencyCheck() { QList list; QList::Iterator it_a; QList::Iterator it_sch; QList::Iterator it_p; QList::Iterator it_t; QList::Iterator it_r; QStringList accountRebuild; QMap interestAccounts; MyMoneyAccount parent; MyMoneyAccount child; MyMoneyAccount toplevel; QString parentId; QStringList rc; int problemCount = 0; int unfixedCount = 0; QString problemAccount; // check that we have a storage object d->checkTransaction(Q_FUNC_INFO); // get the current list of accounts accountList(list); // add the standard accounts list << MyMoneyFile::instance()->asset(); list << MyMoneyFile::instance()->liability(); list << MyMoneyFile::instance()->income(); list << MyMoneyFile::instance()->expense(); for (it_a = list.begin(); it_a != list.end(); ++it_a) { // no more checks for standard accounts if (isStandardAccount((*it_a).id())) { continue; } switch ((*it_a).accountGroup()) { case Account::Type::Asset: toplevel = asset(); break; case Account::Type::Liability: toplevel = liability(); break; case Account::Type::Expense: toplevel = expense(); break; case Account::Type::Income: toplevel = income(); break; case Account::Type::Equity: toplevel = equity(); break; default: qWarning("%s:%d This should never happen!", __FILE__ , __LINE__); break; } // check for loops in the hierarchy parentId = (*it_a).parentAccountId(); try { bool dropOut = false; while (!isStandardAccount(parentId) && !dropOut) { parent = account(parentId); if (parent.id() == (*it_a).id()) { // parent loops, so we need to re-parent to toplevel account // find parent account in our list problemCount++; QList::Iterator it_b; for (it_b = list.begin(); it_b != list.end(); ++it_b) { if ((*it_b).id() == parent.id()) { if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); rc << i18n(" * Loop detected between this account and account '%1'.", (*it_b).name()); rc << i18n(" Reparenting account '%2' to top level account '%1'.", toplevel.name(), (*it_a).name()); (*it_a).setParentAccountId(toplevel.id()); if (accountRebuild.contains(toplevel.id()) == 0) accountRebuild << toplevel.id(); if (accountRebuild.contains((*it_a).id()) == 0) accountRebuild << (*it_a).id(); dropOut = true; break; } } } } parentId = parent.parentAccountId(); } } catch (const MyMoneyException &) { // if we don't know about a parent, we catch it later } // check that the parent exists parentId = (*it_a).parentAccountId(); try { parent = account(parentId); if ((*it_a).accountGroup() != parent.accountGroup()) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } // the parent belongs to a different group, so we reconnect to the // master group account (asset, liability, etc) to which this account // should belong and update it in the engine. rc << i18n(" * Parent account '%1' belongs to a different group.", parent.name()); rc << i18n(" New parent account is the top level account '%1'.", toplevel.name()); (*it_a).setParentAccountId(toplevel.id()); // make sure to rebuild the sub-accounts of the top account // and the one we removed this account from if (accountRebuild.contains(toplevel.id()) == 0) accountRebuild << toplevel.id(); if (accountRebuild.contains(parent.id()) == 0) accountRebuild << parent.id(); } else if (!parent.accountList().contains((*it_a).id())) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } // parent exists, but does not have a reference to the account rc << i18n(" * Parent account '%1' does not contain '%2' as sub-account.", parent.name(), problemAccount); if (accountRebuild.contains(parent.id()) == 0) accountRebuild << parent.id(); } } catch (const MyMoneyException &) { // apparently, the parent does not exist anymore. we reconnect to the // master group account (asset, liability, etc) to which this account // should belong and update it in the engine. problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * The parent with id %1 does not exist anymore.", parentId); rc << i18n(" New parent account is the top level account '%1'.", toplevel.name()); (*it_a).setParentAccountId(toplevel.id()); // make sure to rebuild the sub-accounts of the top account if (accountRebuild.contains(toplevel.id()) == 0) accountRebuild << toplevel.id(); } // now check that all the children exist and have the correct type foreach (const auto accountID, (*it_a).accountList()) { // check that the child exists try { child = account(accountID); if (child.parentAccountId() != (*it_a).id()) { throw MYMONEYEXCEPTION_CSTRING("Child account has a different parent"); } } catch (const MyMoneyException &) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * Child account with id %1 does not exist anymore.", accountID); rc << i18n(" The child account list will be reconstructed."); if (accountRebuild.contains((*it_a).id()) == 0) accountRebuild << (*it_a).id(); } } // see if it is a loan account. if so, remember the assigned interest account if ((*it_a).isLoan()) { MyMoneyAccountLoan loan(*it_a); if (!loan.interestAccountId().isEmpty()) { interestAccounts[loan.interestAccountId()] = true; } try { payee(loan.payee()); } catch (const MyMoneyException &) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * The payee with id %1 referenced by the loan does not exist anymore.", loan.payee()); rc << i18n(" The payee will be removed."); // remove the payee - the account will be modified in the engine later (*it_a).deletePair("payee"); } } // check if it is a category and set the date to 1900-01-01 if different if ((*it_a).isIncomeExpense()) { if (((*it_a).openingDate().isValid() == false) || ((*it_a).openingDate() != QDate(1900, 1, 1))) { (*it_a).setOpeningDate(QDate(1900, 1, 1)); } } // check for clear text online password in the online settings if (!(*it_a).onlineBankingSettings().value("password").isEmpty()) { if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * Older versions of KMyMoney stored an OFX password for this account in cleartext."); rc << i18n(" Please open it in the account editor (Account/Edit account) once and press OK."); rc << i18n(" This will store the password in the KDE wallet and remove the cleartext version."); ++unfixedCount; } // if the account was modified, we need to update it in the engine if (!(d->m_storage->account((*it_a).id()) == (*it_a))) { try { d->m_storage->modifyAccount(*it_a, true); } catch (const MyMoneyException &) { rc << i18n(" * Unable to update account data in engine."); return rc; } } } if (accountRebuild.count() != 0) { rc << i18n("* Reconstructing the child lists for"); } // clear the affected lists for (it_a = list.begin(); it_a != list.end(); ++it_a) { if (accountRebuild.contains((*it_a).id())) { rc << QString(" %1").arg((*it_a).name()); // clear the account list (*it_a).removeAccountIds(); } } // reconstruct the lists for (it_a = list.begin(); it_a != list.end(); ++it_a) { QList::Iterator it; parentId = (*it_a).parentAccountId(); if (accountRebuild.contains(parentId)) { for (it = list.begin(); it != list.end(); ++it) { if ((*it).id() == parentId) { (*it).addAccountId((*it_a).id()); break; } } } } // update the engine objects for (it_a = list.begin(); it_a != list.end(); ++it_a) { if (accountRebuild.contains((*it_a).id())) { try { d->m_storage->modifyAccount(*it_a, true); } catch (const MyMoneyException &) { rc << i18n(" * Unable to update account data for account %1 in engine", (*it_a).name()); } } } // For some reason, files exist with invalid ids. This has been found in the payee id // so we fix them here QList pList = payeeList(); QMappayeeConversionMap; for (it_p = pList.begin(); it_p != pList.end(); ++it_p) { if ((*it_p).id().length() > 7) { // found one of those with an invalid ids // create a new one and store it in the map. MyMoneyPayee payee = (*it_p); payee.clearId(); d->m_storage->addPayee(payee); payeeConversionMap[(*it_p).id()] = payee.id(); rc << i18n(" * Payee %1 recreated with fixed id", payee.name()); ++problemCount; } } // Fix the transactions MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); const auto tList = d->m_storage->transactionList(filter); // Generate the list of interest accounts for (const auto& transaction : tList) { const auto splits = transaction.splits(); for (const auto& split : splits) { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) interestAccounts[split.accountId()] = true; } } QSet supportedAccountTypes; supportedAccountTypes << Account::Type::Checkings << Account::Type::Savings << Account::Type::Cash << Account::Type::CreditCard << Account::Type::Asset << Account::Type::Liability; QSet reportedUnsupportedAccounts; for (const auto& transaction : tList) { MyMoneyTransaction t = transaction; bool tChanged = false; QDate accountOpeningDate; QStringList accountList; const auto splits = t.splits(); foreach (const auto split, splits) { bool sChanged = false; MyMoneySplit s = split; if (payeeConversionMap.find(split.payeeId()) != payeeConversionMap.end()) { s.setPayeeId(payeeConversionMap[s.payeeId()]); sChanged = true; rc << i18n(" * Payee id updated in split of transaction '%1'.", t.id()); ++problemCount; } try { const auto acc = this->account(s.accountId()); // compute the newest opening date of all accounts involved in the transaction // in case the newest opening date is newer than the transaction post date, do one // of the following: // // a) for category and stock accounts: update the opening date of the account // b) for account types where the user cannot modify the opening date through // the UI issue a warning (for each account only once) // c) others will be caught later if (!acc.isIncomeExpense() && !acc.isInvest()) { if (acc.openingDate() > t.postDate()) { if (!accountOpeningDate.isValid() || acc.openingDate() > accountOpeningDate) { accountOpeningDate = acc.openingDate(); } accountList << this->accountToCategory(acc.id()); if (!supportedAccountTypes.contains(acc.accountType()) && !reportedUnsupportedAccounts.contains(acc.id())) { rc << i18n(" * Opening date of Account '%1' cannot be changed to support transaction '%2' post date.", this->accountToCategory(acc.id()), t.id()); reportedUnsupportedAccounts << acc.id(); ++unfixedCount; } } } else { if (acc.openingDate() > t.postDate()) { rc << i18n(" * Transaction '%1' post date '%2' is older than opening date '%4' of account '%3'.", t.id(), t.postDate().toString(Qt::ISODate), this->accountToCategory(acc.id()), acc.openingDate().toString(Qt::ISODate)); rc << i18n(" Account opening date updated."); MyMoneyAccount newAcc = acc; newAcc.setOpeningDate(t.postDate()); this->modifyAccount(newAcc); ++problemCount; } } // make sure, that shares and value have the same number if they // represent the same currency. if (t.commodity() == acc.currencyId() && s.shares().reduce() != s.value().reduce()) { // use the value as master if the transaction is balanced if (t.splitSum().isZero()) { s.setShares(s.value()); rc << i18n(" * shares set to value in split of transaction '%1'.", t.id()); } else { s.setValue(s.shares()); rc << i18n(" * value set to shares in split of transaction '%1'.", t.id()); } sChanged = true; ++problemCount; } } catch (const MyMoneyException &) { rc << i18n(" * Split %2 in transaction '%1' contains a reference to invalid account %3. Please fix manually.", t.id(), split.id(), split.accountId()); ++unfixedCount; } // make sure the interest splits are marked correct as such if (interestAccounts.find(s.accountId()) != interestAccounts.end() && s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); sChanged = true; rc << i18n(" * action marked as interest in split of transaction '%1'.", t.id()); ++problemCount; } if (sChanged) { tChanged = true; t.modifySplit(s); } } // make sure that the transaction's post date is valid if (!t.postDate().isValid()) { tChanged = true; t.setPostDate(t.entryDate().isValid() ? t.entryDate() : QDate::currentDate()); rc << i18n(" * Transaction '%1' has an invalid post date.", t.id()); rc << i18n(" The post date was updated to '%1'.", QLocale().toString(t.postDate(), QLocale::ShortFormat)); ++problemCount; } // check if the transaction's post date is after the opening date // of all accounts involved in the transaction. In case it is not, // issue a warning with the details about the transaction incl. // the account names and dates involved if (accountOpeningDate.isValid() && t.postDate() < accountOpeningDate) { QDate originalPostDate = t.postDate(); #if 0 // for now we do not activate the logic to move the post date to a later // point in time. This could cause some severe trouble if you have lots // of ancient data collected with older versions of KMyMoney that did not // enforce certain conditions like we do now. t.setPostDate(accountOpeningDate); tChanged = true; // copy the price information for investments to the new date QList::const_iterator it_t; foreach (const auto split, t.splits()) { if ((split.action() != "Buy") && (split.action() != "Reinvest")) { continue; } QString id = split.accountId(); auto acc = this->account(id); MyMoneySecurity sec = this->security(acc.currencyId()); MyMoneyPrice price(acc.currencyId(), sec.tradingCurrency(), t.postDate(), split.price(), "Transaction"); this->addPrice(price); break; } #endif rc << i18n(" * Transaction '%1' has a post date '%2' before one of the referenced account's opening date.", t.id(), QLocale().toString(originalPostDate, QLocale::ShortFormat)); rc << i18n(" Referenced accounts: %1", accountList.join(",")); rc << i18n(" The post date was not updated to '%1'.", QLocale().toString(accountOpeningDate, QLocale::ShortFormat)); ++unfixedCount; } if (tChanged) { d->m_storage->modifyTransaction(t); } } // Fix the schedules QList schList = scheduleList(); for (it_sch = schList.begin(); it_sch != schList.end(); ++it_sch) { MyMoneySchedule sch = (*it_sch); MyMoneyTransaction t = sch.transaction(); auto tChanged = false; foreach (const auto split, t.splits()) { MyMoneySplit s = split; bool sChanged = false; if (payeeConversionMap.find(split.payeeId()) != payeeConversionMap.end()) { s.setPayeeId(payeeConversionMap[s.payeeId()]); sChanged = true; rc << i18n(" * Payee id updated in split of schedule '%1'.", (*it_sch).name()); ++problemCount; } if (!split.value().isZero() && split.shares().isZero()) { s.setShares(s.value()); sChanged = true; rc << i18n(" * Split in scheduled transaction '%1' contained value != 0 and shares == 0.", (*it_sch).name()); rc << i18n(" Shares set to value."); ++problemCount; } // make sure, we don't have a bankid stored with a split in a schedule if (!split.bankID().isEmpty()) { s.setBankID(QString()); sChanged = true; rc << i18n(" * Removed bankid from split in scheduled transaction '%1'.", (*it_sch).name()); ++problemCount; } // make sure, that shares and value have the same number if they // represent the same currency. try { const auto acc = this->account(s.accountId()); if (t.commodity() == acc.currencyId() && s.shares().reduce() != s.value().reduce()) { // use the value as master if the transaction is balanced if (t.splitSum().isZero()) { s.setShares(s.value()); rc << i18n(" * shares set to value in split in schedule '%1'.", (*it_sch).name()); } else { s.setValue(s.shares()); rc << i18n(" * value set to shares in split in schedule '%1'.", (*it_sch).name()); } sChanged = true; ++problemCount; } } catch (const MyMoneyException &) { rc << i18n(" * Split %2 in schedule '%1' contains a reference to invalid account %3. Please fix manually.", (*it_sch).name(), split.id(), split.accountId()); ++unfixedCount; } if (sChanged) { t.modifySplit(s); tChanged = true; } } if (tChanged) { sch.setTransaction(t); d->m_storage->modifySchedule(sch); } } // Fix the reports QList rList = reportList(); for (it_r = rList.begin(); it_r != rList.end(); ++it_r) { MyMoneyReport r = *it_r; QStringList payeeList; (*it_r).payees(payeeList); bool rChanged = false; for (auto it_payee = payeeList.begin(); it_payee != payeeList.end(); ++it_payee) { if (payeeConversionMap.find(*it_payee) != payeeConversionMap.end()) { rc << i18n(" * Payee id updated in report '%1'.", (*it_r).name()); ++problemCount; r.removeReference(*it_payee); r.addPayee(payeeConversionMap[*it_payee]); rChanged = true; } } if (rChanged) { d->m_storage->modifyReport(r); } } // erase old payee ids QMap::Iterator it_m; for (it_m = payeeConversionMap.begin(); it_m != payeeConversionMap.end(); ++it_m) { MyMoneyPayee payee = this->payee(it_m.key()); removePayee(payee); rc << i18n(" * Payee '%1' removed.", payee.id()); ++problemCount; } //look for accounts which have currencies other than the base currency but no price on the opening date //all accounts using base currency are excluded, since that's the base used for foreign currency calculation //thus it is considered as always present //accounts that represent Income/Expense categories are also excluded as price is irrelevant for their //fake opening date since a forex rate is required for all multi-currency transactions //get all currencies in use QStringList currencyList; QList accountForeignCurrency; QList accList; accountList(accList); QList::const_iterator account_it; for (account_it = accList.constBegin(); account_it != accList.constEnd(); ++account_it) { MyMoneyAccount account = *account_it; if (!account.isIncomeExpense() && !currencyList.contains(account.currencyId()) && account.currencyId() != baseCurrency().id() && !account.currencyId().isEmpty()) { //add the currency and the account-currency pair currencyList.append(account.currencyId()); accountForeignCurrency.append(account); } } MyMoneyPriceList pricesList = priceList(); QMap securityPriceDate; //get the first date of the price for each security MyMoneyPriceList::const_iterator prices_it; for (prices_it = pricesList.constBegin(); prices_it != pricesList.constEnd(); ++prices_it) { MyMoneyPrice firstPrice = (*((*prices_it).constBegin())); //only check the price if the currency is in use if (currencyList.contains(firstPrice.from()) || currencyList.contains(firstPrice.to())) { //check the security in the from field //if it is there, check if it is older QPair pricePair = qMakePair(firstPrice.from(), firstPrice.to()); securityPriceDate[pricePair] = firstPrice.date(); } } //compare the dates with the opening dates of the accounts using each currency QList::const_iterator accForeignList_it; bool firstInvProblem = true; for (accForeignList_it = accountForeignCurrency.constBegin(); accForeignList_it != accountForeignCurrency.constEnd(); ++accForeignList_it) { //setup the price pair correctly QPair pricePair; //setup the reverse, which can also be used for rate conversion QPair reversePricePair; if ((*accForeignList_it).isInvest()) { //if it is a stock, we have to search for a price from its stock to the currency of the account QString securityId = (*accForeignList_it).currencyId(); QString tradingCurrencyId = security(securityId).tradingCurrency(); pricePair = qMakePair(securityId, tradingCurrencyId); reversePricePair = qMakePair(tradingCurrencyId, securityId); } else { //if it is a regular account we search for a price from the currency of the account to the base currency QString currency = (*accForeignList_it).currencyId(); QString baseCurrencyId = baseCurrency().id(); pricePair = qMakePair(currency, baseCurrencyId); reversePricePair = qMakePair(baseCurrencyId, currency); } //compare the first price with the opening date of the account if ((!securityPriceDate.contains(pricePair) || securityPriceDate.value(pricePair) > (*accForeignList_it).openingDate()) && (!securityPriceDate.contains(reversePricePair) || securityPriceDate.value(reversePricePair) > (*accForeignList_it).openingDate())) { if (firstInvProblem) { firstInvProblem = false; rc << i18n("* Potential problem with securities/currencies"); } QDate openingDate = (*accForeignList_it).openingDate(); MyMoneySecurity secError = security((*accForeignList_it).currencyId()); if (!(*accForeignList_it).isInvest()) { rc << i18n(" * The account '%1' in currency '%2' has no price set for the opening date '%3'.", (*accForeignList_it).name(), secError.name(), openingDate.toString(Qt::ISODate)); rc << i18n(" Please enter a price for the currency on or before the opening date."); } else { rc << i18n(" * The security '%1' has no price set for the opening date '%2'.", (*accForeignList_it).name(), openingDate.toString(Qt::ISODate)); rc << i18n(" Please enter a price for the security on or before the opening date."); } ++unfixedCount; } } // Fix the budgets that somehow still reference invalid accounts QString problemBudget; QList bList = budgetList(); for (QList::const_iterator it_b = bList.constBegin(); it_b != bList.constEnd(); ++it_b) { MyMoneyBudget b = *it_b; QList baccounts = b.getaccounts(); bool bChanged = false; for (QList::const_iterator it_bacc = baccounts.constBegin(); it_bacc != baccounts.constEnd(); ++it_bacc) { try { account((*it_bacc).id()); } catch (const MyMoneyException &) { problemCount++; if (problemBudget != b.name()) { problemBudget = b.name(); rc << i18n("* Problem with budget '%1'", problemBudget); } rc << i18n(" * The account with id %1 referenced by the budget does not exist anymore.", (*it_bacc).id()); rc << i18n(" The account reference will be removed."); // remove the reference to the account b.removeReference((*it_bacc).id()); bChanged = true; } } if (bChanged) { d->m_storage->modifyBudget(b); } } // add more checks here if (problemCount == 0 && unfixedCount == 0) { rc << i18n("Finished: data is consistent."); } else { const QString problemsCorrected = i18np("%1 problem corrected.", "%1 problems corrected.", problemCount); const QString problemsRemaining = i18np("%1 problem still present.", "%1 problems still present.", unfixedCount); rc << QString(); rc << i18nc("%1 is a string, e.g. 7 problems corrected; %2 is a string, e.g. 3 problems still present", "Finished: %1 %2", problemsCorrected, problemsRemaining); } return rc; } QString MyMoneyFile::createCategory(const MyMoneyAccount& base, const QString& name) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount parent = base; QString categoryText; if (base.id() != expense().id() && base.id() != income().id()) throw MYMONEYEXCEPTION_CSTRING("Invalid base category"); QStringList subAccounts = name.split(AccountSeparator); QStringList::Iterator it; for (it = subAccounts.begin(); it != subAccounts.end(); ++it) { MyMoneyAccount categoryAccount; categoryAccount.setName(*it); categoryAccount.setAccountType(base.accountType()); if (it == subAccounts.begin()) categoryText += *it; else categoryText += (AccountSeparator + *it); // Only create the account if it doesn't exist try { QString categoryId = categoryToAccount(categoryText); if (categoryId.isEmpty()) addAccount(categoryAccount, parent); else { categoryAccount = account(categoryId); } } catch (const MyMoneyException &e) { qDebug("Unable to add account %s, %s, %s: %s", qPrintable(categoryAccount.name()), qPrintable(parent.name()), qPrintable(categoryText), e.what()); } parent = categoryAccount; } return categoryToAccount(name); } QString MyMoneyFile::checkCategory(const QString& name, const MyMoneyMoney& value, const MyMoneyMoney& value2) { QString accountId; MyMoneyAccount newAccount; bool found = true; if (!name.isEmpty()) { // The category might be constructed with an arbitrary depth (number of // colon delimited fields). We try to find a parent account within this // hierarchy by searching the following sequence: // // aaaa:bbbb:cccc:ddddd // // 1. search aaaa:bbbb:cccc:dddd, create nothing // 2. search aaaa:bbbb:cccc , create dddd // 3. search aaaa:bbbb , create cccc:dddd // 4. search aaaa , create bbbb:cccc:dddd // 5. don't search , create aaaa:bbbb:cccc:dddd newAccount.setName(name); QString accName; // part to be created (right side in above list) QString parent(name); // a possible parent part (left side in above list) do { accountId = categoryToAccount(parent); if (accountId.isEmpty()) { found = false; // prepare next step if (!accName.isEmpty()) accName.prepend(':'); accName.prepend(parent.section(':', -1)); newAccount.setName(accName); parent = parent.section(':', 0, -2); } else if (!accName.isEmpty()) { newAccount.setParentAccountId(accountId); } } while (!parent.isEmpty() && accountId.isEmpty()); // if we did not find the category, we create it if (!found) { MyMoneyAccount parentAccount; if (newAccount.parentAccountId().isEmpty()) { if (!value.isNegative() && value2.isNegative()) parentAccount = income(); else parentAccount = expense(); } else { parentAccount = account(newAccount.parentAccountId()); } newAccount.setAccountType((!value.isNegative() && value2.isNegative()) ? Account::Type::Income : Account::Type::Expense); MyMoneyAccount brokerage; // clear out the parent id, because createAccount() does not like that newAccount.setParentAccountId(QString()); createAccount(newAccount, parentAccount, brokerage, MyMoneyMoney()); accountId = newAccount.id(); } } return accountId; } void MyMoneyFile::addSecurity(MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->addSecurity(security); d->m_changeSet += MyMoneyNotification(File::Mode::Add, security); } void MyMoneyFile::modifySecurity(const MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifySecurity(security); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, security); } void MyMoneyFile::removeSecurity(const MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); // FIXME check that security is not referenced by other object d->m_storage->removeSecurity(security); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, security); } MyMoneySecurity MyMoneyFile::security(const QString& id) const { if (Q_UNLIKELY(id.isEmpty())) return baseCurrency(); return d->m_storage->security(id); } QList MyMoneyFile::securityList() const { d->checkStorage(); return d->m_storage->securityList(); } void MyMoneyFile::addCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->addCurrency(currency); d->m_changeSet += MyMoneyNotification(File::Mode::Add, currency); } void MyMoneyFile::modifyCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); // force reload of base currency object if (currency.id() == d->m_baseCurrency.id()) d->m_baseCurrency.clearId(); d->m_storage->modifyCurrency(currency); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, currency); } void MyMoneyFile::removeCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); if (currency.id() == d->m_baseCurrency.id()) throw MYMONEYEXCEPTION_CSTRING("Cannot delete base currency."); // FIXME check that security is not referenced by other object d->m_storage->removeCurrency(currency); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, currency); } MyMoneySecurity MyMoneyFile::currency(const QString& id) const { if (id.isEmpty()) return baseCurrency(); try { const auto currency = d->m_storage->currency(id); if (currency.id().isEmpty()) throw MYMONEYEXCEPTION(QString::fromLatin1("Currency '%1' not found.").arg(id)); return currency; } catch (const MyMoneyException &) { const auto security = d->m_storage->security(id); if (security.id().isEmpty()) { throw MYMONEYEXCEPTION(QString::fromLatin1("Security '%1' not found.").arg(id)); } return security; } } QMap MyMoneyFile::ancientCurrencies() const { QMap ancientCurrencies; ancientCurrencies.insert(MyMoneySecurity("ATS", i18n("Austrian Schilling"), QString::fromUtf8("ÖS")), MyMoneyPrice("ATS", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 137603), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("DEM", i18n("German Mark"), "DM"), MyMoneyPrice("ATS", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 195583), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("FRF", i18n("French Franc"), "FF"), MyMoneyPrice("FRF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 655957), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("ITL", i18n("Italian Lira"), QChar(0x20A4)), MyMoneyPrice("ITL", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100, 193627), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("ESP", i18n("Spanish Peseta"), QString()), MyMoneyPrice("ESP", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000, 166386), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("NLG", i18n("Dutch Guilder"), QString()), MyMoneyPrice("NLG", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 220371), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("BEF", i18n("Belgian Franc"), "Fr"), MyMoneyPrice("BEF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 403399), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("LUF", i18n("Luxembourg Franc"), "Fr"), MyMoneyPrice("LUF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 403399), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("PTE", i18n("Portuguese Escudo"), QString()), MyMoneyPrice("PTE", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000, 200482), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("IEP", i18n("Irish Pound"), QChar(0x00A3)), MyMoneyPrice("IEP", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000000, 787564), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("FIM", i18n("Finnish Markka"), QString()), MyMoneyPrice("FIM", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 594573), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("GRD", i18n("Greek Drachma"), QChar(0x20AF)), MyMoneyPrice("GRD", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100, 34075), QLatin1Literal("KMyMoney"))); // https://en.wikipedia.org/wiki/Bulgarian_lev ancientCurrencies.insert(MyMoneySecurity("BGL", i18n("Bulgarian Lev"), "BGL"), MyMoneyPrice("BGL", "BGN", QDate(1999, 7, 5), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("ROL", i18n("Romanian Leu"), "ROL"), MyMoneyPrice("ROL", "RON", QDate(2005, 6, 30), MyMoneyMoney(1, 10000), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("RUR", i18n("Russian Ruble (old)"), "RUR"), MyMoneyPrice("RUR", "RUB", QDate(1998, 1, 1), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("SIT", i18n("Slovenian Tolar"), "SIT"), MyMoneyPrice("SIT", "EUR", QDate(2006, 12, 31), MyMoneyMoney(1, 23964), QLatin1Literal("KMyMoney"))); // Source: https://en.wikipedia.org/wiki/Turkish_lira ancientCurrencies.insert(MyMoneySecurity("TRL", i18n("Turkish Lira (old)"), "TL"), MyMoneyPrice("TRL", "TRY", QDate(2004, 12, 31), MyMoneyMoney(1, 1000000), QLatin1Literal("KMyMoney"))); // Source: https://www.focus.de/finanzen/news/malta-und-zypern_aid_66058.html ancientCurrencies.insert(MyMoneySecurity("MTL", i18n("Maltese Lira"), "MTL"), MyMoneyPrice("MTL", "EUR", QDate(2008, 1, 1), MyMoneyMoney(429300, 1000000), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("CYP", i18n("Cyprus Pound"), QString("C%1").arg(QChar(0x00A3))), MyMoneyPrice("CYP", "EUR", QDate(2008, 1, 1), MyMoneyMoney(585274, 1000000), QLatin1Literal("KMyMoney"))); // Source: https://www.focus.de/finanzen/news/waehrungszone-slowakei-ist-neuer-euro-staat_aid_359025.html ancientCurrencies.insert(MyMoneySecurity("SKK", i18n("Slovak Koruna"), "SKK"), MyMoneyPrice("SKK", "EUR", QDate(2008, 12, 31), MyMoneyMoney(1000, 30126), QLatin1Literal("KMyMoney"))); // Source: https://en.wikipedia.org/wiki/Mozambican_metical ancientCurrencies.insert(MyMoneySecurity("MZM", i18n("Mozambique Metical"), "MT"), MyMoneyPrice("MZM", "MZN", QDate(2006, 7, 1), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); // Source https://en.wikipedia.org/wiki/Azerbaijani_manat ancientCurrencies.insert(MyMoneySecurity("AZM", i18n("Azerbaijani Manat"), "m."), MyMoneyPrice("AZM", "AZN", QDate(2006, 1, 1), MyMoneyMoney(1, 5000), QLatin1Literal("KMyMoney"))); // Source: https://en.wikipedia.org/wiki/Litas ancientCurrencies.insert(MyMoneySecurity("LTL", i18n("Lithuanian Litas"), "Lt"), MyMoneyPrice("LTL", "EUR", QDate(2015, 1, 1), MyMoneyMoney(100000, 345280), QLatin1Literal("KMyMoney"))); // Source: https://en.wikipedia.org/wiki/Belarusian_ruble ancientCurrencies.insert(MyMoneySecurity("BYR", i18n("Belarusian Ruble (old)"), "BYR"), MyMoneyPrice("BYR", "BYN", QDate(2016, 7, 1), MyMoneyMoney(1, 10000), QLatin1Literal("KMyMoney"))); return ancientCurrencies; } QList MyMoneyFile::availableCurrencyList() const { QList currencyList; currencyList.append(MyMoneySecurity("AFA", i18n("Afghanistan Afghani"))); currencyList.append(MyMoneySecurity("ALL", i18n("Albanian Lek"))); currencyList.append(MyMoneySecurity("ANG", i18n("Netherland Antillian Guilder"))); currencyList.append(MyMoneySecurity("DZD", i18n("Algerian Dinar"))); currencyList.append(MyMoneySecurity("ADF", i18n("Andorran Franc"))); currencyList.append(MyMoneySecurity("ADP", i18n("Andorran Peseta"))); currencyList.append(MyMoneySecurity("AOA", i18n("Angolan Kwanza"), "Kz")); currencyList.append(MyMoneySecurity("ARS", i18n("Argentine Peso"), "$")); currencyList.append(MyMoneySecurity("AWG", i18n("Aruban Florin"))); currencyList.append(MyMoneySecurity("AUD", i18n("Australian Dollar"), "$")); currencyList.append(MyMoneySecurity("AZN", i18n("Azerbaijani Manat"), "m.")); currencyList.append(MyMoneySecurity("BSD", i18n("Bahamian Dollar"), "$")); currencyList.append(MyMoneySecurity("BHD", i18n("Bahraini Dinar"), "BHD", 1000)); currencyList.append(MyMoneySecurity("BDT", i18n("Bangladeshi Taka"))); currencyList.append(MyMoneySecurity("BBD", i18n("Barbados Dollar"), "$")); currencyList.append(MyMoneySecurity("BTC", i18n("Bitcoin"), "BTC")); currencyList.append(MyMoneySecurity("BYN", i18n("Belarusian Ruble"), "Br")); currencyList.append(MyMoneySecurity("BZD", i18n("Belize Dollar"), "$")); currencyList.append(MyMoneySecurity("BMD", i18n("Bermudian Dollar"), "$")); currencyList.append(MyMoneySecurity("BTN", i18n("Bhutan Ngultrum"))); currencyList.append(MyMoneySecurity("BOB", i18n("Bolivian Boliviano"))); currencyList.append(MyMoneySecurity("BAM", i18n("Bosnian Convertible Mark"))); currencyList.append(MyMoneySecurity("BWP", i18n("Botswana Pula"))); currencyList.append(MyMoneySecurity("BRL", i18n("Brazilian Real"), "R$")); currencyList.append(MyMoneySecurity("GBP", i18n("British Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("BND", i18n("Brunei Dollar"), "$")); currencyList.append(MyMoneySecurity("BGN", i18n("Bulgarian Lev (new)"))); currencyList.append(MyMoneySecurity("BIF", i18n("Burundi Franc"))); currencyList.append(MyMoneySecurity("XAF", i18n("CFA Franc BEAC"))); currencyList.append(MyMoneySecurity("XOF", i18n("CFA Franc BCEAO"))); currencyList.append(MyMoneySecurity("XPF", i18n("CFP Franc Pacifique"), "F", 1, 100)); currencyList.append(MyMoneySecurity("KHR", i18n("Cambodia Riel"))); currencyList.append(MyMoneySecurity("CAD", i18n("Canadian Dollar"), "$")); currencyList.append(MyMoneySecurity("CVE", i18n("Cape Verde Escudo"))); currencyList.append(MyMoneySecurity("KYD", i18n("Cayman Islands Dollar"), "$")); currencyList.append(MyMoneySecurity("CLP", i18n("Chilean Peso"))); currencyList.append(MyMoneySecurity("CNY", i18n("Chinese Yuan Renminbi"))); currencyList.append(MyMoneySecurity("COP", i18n("Colombian Peso"))); currencyList.append(MyMoneySecurity("KMF", i18n("Comoros Franc"))); currencyList.append(MyMoneySecurity("CRC", i18n("Costa Rican Colon"), QChar(0x20A1))); currencyList.append(MyMoneySecurity("HRK", i18n("Croatian Kuna"))); currencyList.append(MyMoneySecurity("CUP", i18n("Cuban Peso"))); currencyList.append(MyMoneySecurity("CUC", i18n("Cuban Convertible Peso"))); currencyList.append(MyMoneySecurity("CZK", i18n("Czech Koruna"))); currencyList.append(MyMoneySecurity("DKK", i18n("Danish Krone"), "kr")); currencyList.append(MyMoneySecurity("DJF", i18n("Djibouti Franc"))); currencyList.append(MyMoneySecurity("DOP", i18n("Dominican Peso"))); currencyList.append(MyMoneySecurity("XCD", i18n("East Caribbean Dollar"), "$")); currencyList.append(MyMoneySecurity("EGP", i18n("Egyptian Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("SVC", i18n("El Salvador Colon"))); currencyList.append(MyMoneySecurity("ERN", i18n("Eritrean Nakfa"))); currencyList.append(MyMoneySecurity("EEK", i18n("Estonian Kroon"))); currencyList.append(MyMoneySecurity("ETB", i18n("Ethiopian Birr"))); currencyList.append(MyMoneySecurity("EUR", i18n("Euro"), QChar(0x20ac))); currencyList.append(MyMoneySecurity("FKP", i18n("Falkland Islands Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("FJD", i18n("Fiji Dollar"), "$")); currencyList.append(MyMoneySecurity("GMD", i18n("Gambian Dalasi"))); currencyList.append(MyMoneySecurity("GEL", i18n("Georgian Lari"))); currencyList.append(MyMoneySecurity("GHC", i18n("Ghanaian Cedi"))); currencyList.append(MyMoneySecurity("GIP", i18n("Gibraltar Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("GTQ", i18n("Guatemalan Quetzal"))); currencyList.append(MyMoneySecurity("GWP", i18n("Guinea-Bissau Peso"))); currencyList.append(MyMoneySecurity("GYD", i18n("Guyanan Dollar"), "$")); currencyList.append(MyMoneySecurity("HTG", i18n("Haitian Gourde"))); currencyList.append(MyMoneySecurity("HNL", i18n("Honduran Lempira"))); currencyList.append(MyMoneySecurity("HKD", i18n("Hong Kong Dollar"), "$")); currencyList.append(MyMoneySecurity("HUF", i18n("Hungarian Forint"), "HUF", 1, 100)); currencyList.append(MyMoneySecurity("ISK", i18n("Iceland Krona"))); - currencyList.append(MyMoneySecurity("INR", i18n("Indian Rupee"), QChar(0x20A8))); + currencyList.append(MyMoneySecurity("INR", i18n("Indian Rupee"), QChar(0x20B9))); currencyList.append(MyMoneySecurity("IDR", i18n("Indonesian Rupiah"), "IDR", 1, 0, 10)); currencyList.append(MyMoneySecurity("IRR", i18n("Iranian Rial"), "IRR", 1)); currencyList.append(MyMoneySecurity("IQD", i18n("Iraqi Dinar"), "IQD", 1000)); currencyList.append(MyMoneySecurity("ILS", i18n("Israeli New Shekel"), QChar(0x20AA))); currencyList.append(MyMoneySecurity("JMD", i18n("Jamaican Dollar"), "$")); currencyList.append(MyMoneySecurity("JPY", i18n("Japanese Yen"), QChar(0x00A5), 1)); currencyList.append(MyMoneySecurity("JOD", i18n("Jordanian Dinar"), "JOD", 1000)); currencyList.append(MyMoneySecurity("KZT", i18n("Kazakhstan Tenge"))); currencyList.append(MyMoneySecurity("KES", i18n("Kenyan Shilling"))); currencyList.append(MyMoneySecurity("KWD", i18n("Kuwaiti Dinar"), "KWD", 1000)); currencyList.append(MyMoneySecurity("KGS", i18n("Kyrgyzstan Som"))); currencyList.append(MyMoneySecurity("LAK", i18n("Laos Kip"), QChar(0x20AD))); currencyList.append(MyMoneySecurity("LVL", i18n("Latvian Lats"))); currencyList.append(MyMoneySecurity("LBP", i18n("Lebanese Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("LSL", i18n("Lesotho Loti"))); currencyList.append(MyMoneySecurity("LRD", i18n("Liberian Dollar"), "$")); currencyList.append(MyMoneySecurity("LYD", i18n("Libyan Dinar"), "LYD", 1000)); currencyList.append(MyMoneySecurity("MOP", i18n("Macau Pataca"))); currencyList.append(MyMoneySecurity("MKD", i18n("Macedonian Denar"))); currencyList.append(MyMoneySecurity("MGF", i18n("Malagasy Franc"), "MGF", 500)); currencyList.append(MyMoneySecurity("MWK", i18n("Malawi Kwacha"))); currencyList.append(MyMoneySecurity("MYR", i18n("Malaysian Ringgit"))); currencyList.append(MyMoneySecurity("MVR", i18n("Maldive Rufiyaa"))); currencyList.append(MyMoneySecurity("MLF", i18n("Mali Republic Franc"))); currencyList.append(MyMoneySecurity("MRO", i18n("Mauritanian Ouguiya"), "MRO", 5)); currencyList.append(MyMoneySecurity("MUR", i18n("Mauritius Rupee"))); currencyList.append(MyMoneySecurity("MXN", i18n("Mexican Peso"), "$")); currencyList.append(MyMoneySecurity("MDL", i18n("Moldavian Leu"))); currencyList.append(MyMoneySecurity("MNT", i18n("Mongolian Tugrik"), QChar(0x20AE))); currencyList.append(MyMoneySecurity("MAD", i18n("Moroccan Dirham"))); currencyList.append(MyMoneySecurity("MZN", i18n("Mozambique Metical"), "MT")); currencyList.append(MyMoneySecurity("MMK", i18n("Myanmar Kyat"))); currencyList.append(MyMoneySecurity("NAD", i18n("Namibian Dollar"), "$")); currencyList.append(MyMoneySecurity("NPR", i18n("Nepalese Rupee"))); currencyList.append(MyMoneySecurity("NZD", i18n("New Zealand Dollar"), "$")); currencyList.append(MyMoneySecurity("NIC", i18n("Nicaraguan Cordoba Oro"))); currencyList.append(MyMoneySecurity("NGN", i18n("Nigerian Naira"), QChar(0x20A6))); currencyList.append(MyMoneySecurity("KPW", i18n("North Korean Won"), QChar(0x20A9))); currencyList.append(MyMoneySecurity("NOK", i18n("Norwegian Kroner"), "kr")); currencyList.append(MyMoneySecurity("OMR", i18n("Omani Rial"), "OMR", 1000)); currencyList.append(MyMoneySecurity("PKR", i18n("Pakistan Rupee"))); currencyList.append(MyMoneySecurity("PAB", i18n("Panamanian Balboa"))); currencyList.append(MyMoneySecurity("PGK", i18n("Papua New Guinea Kina"))); currencyList.append(MyMoneySecurity("PYG", i18n("Paraguay Guarani"))); currencyList.append(MyMoneySecurity("PEN", i18n("Peruvian Nuevo Sol"))); currencyList.append(MyMoneySecurity("PHP", i18n("Philippine Peso"), QChar(0x20B1))); currencyList.append(MyMoneySecurity("PLN", i18n("Polish Zloty"))); currencyList.append(MyMoneySecurity("QAR", i18n("Qatari Rial"))); currencyList.append(MyMoneySecurity("RON", i18n("Romanian Leu (new)"))); currencyList.append(MyMoneySecurity("RUB", i18n("Russian Ruble"))); currencyList.append(MyMoneySecurity("RWF", i18n("Rwanda Franc"))); currencyList.append(MyMoneySecurity("WST", i18n("Samoan Tala"))); currencyList.append(MyMoneySecurity("STD", i18n("Sao Tome and Principe Dobra"))); currencyList.append(MyMoneySecurity("SAR", i18n("Saudi Riyal"))); currencyList.append(MyMoneySecurity("RSD", i18n("Serbian Dinar"))); currencyList.append(MyMoneySecurity("SCR", i18n("Seychelles Rupee"))); currencyList.append(MyMoneySecurity("SLL", i18n("Sierra Leone Leone"))); currencyList.append(MyMoneySecurity("SGD", i18n("Singapore Dollar"), "$")); currencyList.append(MyMoneySecurity("SBD", i18n("Solomon Islands Dollar"), "$")); currencyList.append(MyMoneySecurity("SOS", i18n("Somali Shilling"))); currencyList.append(MyMoneySecurity("ZAR", i18n("South African Rand"))); currencyList.append(MyMoneySecurity("KRW", i18n("South Korean Won"), QChar(0x20A9))); currencyList.append(MyMoneySecurity("LKR", i18n("Sri Lanka Rupee"))); currencyList.append(MyMoneySecurity("SHP", i18n("St. Helena Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("SDD", i18n("Sudanese Dinar"))); currencyList.append(MyMoneySecurity("SRG", i18n("Suriname Guilder"))); currencyList.append(MyMoneySecurity("SZL", i18n("Swaziland Lilangeni"))); currencyList.append(MyMoneySecurity("SEK", i18n("Swedish Krona"))); currencyList.append(MyMoneySecurity("CHF", i18n("Swiss Franc"), "SFr")); currencyList.append(MyMoneySecurity("SYP", i18n("Syrian Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("TWD", i18n("Taiwan Dollar"), "$")); currencyList.append(MyMoneySecurity("TJS", i18n("Tajikistan Somoni"))); currencyList.append(MyMoneySecurity("TZS", i18n("Tanzanian Shilling"))); currencyList.append(MyMoneySecurity("THB", i18n("Thai Baht"), QChar(0x0E3F))); currencyList.append(MyMoneySecurity("TOP", i18n("Tongan Pa'anga"))); currencyList.append(MyMoneySecurity("TTD", i18n("Trinidad and Tobago Dollar"), "$")); currencyList.append(MyMoneySecurity("TND", i18n("Tunisian Dinar"), "TND", 1000)); currencyList.append(MyMoneySecurity("TRY", i18n("Turkish Lira"), QChar(0x20BA))); currencyList.append(MyMoneySecurity("TMM", i18n("Turkmenistan Manat"))); currencyList.append(MyMoneySecurity("USD", i18n("US Dollar"), "$")); currencyList.append(MyMoneySecurity("UGX", i18n("Uganda Shilling"))); currencyList.append(MyMoneySecurity("UAH", i18n("Ukraine Hryvnia"))); currencyList.append(MyMoneySecurity("CLF", i18n("Unidad de Fometo"))); currencyList.append(MyMoneySecurity("AED", i18n("United Arab Emirates Dirham"))); currencyList.append(MyMoneySecurity("UYU", i18n("Uruguayan Peso"))); currencyList.append(MyMoneySecurity("UZS", i18n("Uzbekistani Sum"))); currencyList.append(MyMoneySecurity("VUV", i18n("Vanuatu Vatu"))); currencyList.append(MyMoneySecurity("VEB", i18n("Venezuelan Bolivar"))); currencyList.append(MyMoneySecurity("VND", i18n("Vietnamese Dong"), QChar(0x20AB))); currencyList.append(MyMoneySecurity("ZMK", i18n("Zambian Kwacha"))); currencyList.append(MyMoneySecurity("ZWD", i18n("Zimbabwe Dollar"), "$")); currencyList.append(ancientCurrencies().keys()); // sort the currencies ... qSort(currencyList.begin(), currencyList.end(), [] (const MyMoneySecurity& c1, const MyMoneySecurity& c2) { return c1.name().compare(c2.name()) < 0; }); // ... and add a few precious metals at the ned currencyList.append(MyMoneySecurity("XAU", i18n("Gold"), "XAU", 1000000)); currencyList.append(MyMoneySecurity("XPD", i18n("Palladium"), "XPD", 1000000)); currencyList.append(MyMoneySecurity("XPT", i18n("Platinum"), "XPT", 1000000)); currencyList.append(MyMoneySecurity("XAG", i18n("Silver"), "XAG", 1000000)); return currencyList; } QList MyMoneyFile::currencyList() const { d->checkStorage(); return d->m_storage->currencyList(); } QString MyMoneyFile::foreignCurrency(const QString& first, const QString& second) const { if (baseCurrency().id() == second) return first; return second; } MyMoneySecurity MyMoneyFile::baseCurrency() const { if (d->m_baseCurrency.id().isEmpty()) { QString id = QString(value("kmm-baseCurrency")); if (!id.isEmpty()) d->m_baseCurrency = currency(id); } return d->m_baseCurrency; } void MyMoneyFile::setBaseCurrency(const MyMoneySecurity& curr) { // make sure the currency exists MyMoneySecurity c = currency(curr.id()); if (c.id() != d->m_baseCurrency.id()) { setValue("kmm-baseCurrency", curr.id()); // force reload of base currency cache d->m_baseCurrency = MyMoneySecurity(); } } void MyMoneyFile::addPrice(const MyMoneyPrice& price) { if (price.rate(QString()).isZero()) return; d->checkTransaction(Q_FUNC_INFO); // store the account's which are affected by this price regarding their value d->priceChanged(*this, price); d->m_storage->addPrice(price); } void MyMoneyFile::removePrice(const MyMoneyPrice& price) { d->checkTransaction(Q_FUNC_INFO); // store the account's which are affected by this price regarding their value d->priceChanged(*this, price); d->m_storage->removePrice(price); } MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const { d->checkStorage(); QString to(toId); if (to.isEmpty()) to = value("kmm-baseCurrency"); // if some id is missing, we can return an empty price object if (fromId.isEmpty() || to.isEmpty()) return MyMoneyPrice(); // we don't search our tables if someone asks stupid stuff if (fromId == toId) { return MyMoneyPrice(fromId, toId, date, MyMoneyMoney::ONE, "KMyMoney"); } // if not asking for exact date, try to find the exact date match first, // either the requested price or its reciprocal value. If unsuccessful, it will move // on and look for prices of previous dates MyMoneyPrice rc = d->m_storage->price(fromId, to, date, true); if (!rc.isValid()) { // not found, search 'to-from' rate and use reciprocal value rc = d->m_storage->price(to, fromId, date, true); // not found, search previous dates, if exact date is not needed if (!exactDate && !rc.isValid()) { // search 'from-to' and 'to-from', select the most recent one MyMoneyPrice fromPrice = d->m_storage->price(fromId, to, date, exactDate); MyMoneyPrice toPrice = d->m_storage->price(to, fromId, date, exactDate); // check first whether both prices are valid if (fromPrice.isValid() && toPrice.isValid()) { if (fromPrice.date() >= toPrice.date()) { // if 'from-to' is newer or the same date, prefer that one rc = fromPrice; } else { // otherwise, use the reciprocal price rc = toPrice; } } else if (fromPrice.isValid()) { // check if any of the prices is valid, return that one rc = fromPrice; } else if (toPrice.isValid()) { rc = toPrice; } } } return rc; } MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId) const { return price(fromId, toId, QDate::currentDate(), false); } MyMoneyPrice MyMoneyFile::price(const QString& fromId) const { return price(fromId, QString(), QDate::currentDate(), false); } MyMoneyPriceList MyMoneyFile::priceList() const { d->checkStorage(); return d->m_storage->priceList(); } bool MyMoneyFile::hasAccount(const QString& id, const QString& name) const { const auto accounts = account(id).accountList(); for (const auto& acc : accounts) { if (account(acc).name().compare(name) == 0) return true; } return false; } QList MyMoneyFile::reportList() const { d->checkStorage(); return d->m_storage->reportList(); } void MyMoneyFile::addReport(MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->addReport(report); } void MyMoneyFile::modifyReport(const MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifyReport(report); } unsigned MyMoneyFile::countReports() const { d->checkStorage(); return d->m_storage->countReports(); } MyMoneyReport MyMoneyFile::report(const QString& id) const { d->checkStorage(); return d->m_storage->report(id); } void MyMoneyFile::removeReport(const MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->removeReport(report); } QList MyMoneyFile::budgetList() const { d->checkStorage(); return d->m_storage->budgetList(); } void MyMoneyFile::addBudget(MyMoneyBudget &budget) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->addBudget(budget); } MyMoneyBudget MyMoneyFile::budgetByName(const QString& name) const { d->checkStorage(); return d->m_storage->budgetByName(name); } void MyMoneyFile::modifyBudget(const MyMoneyBudget& budget) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifyBudget(budget); } unsigned MyMoneyFile::countBudgets() const { d->checkStorage(); return d->m_storage->countBudgets(); } MyMoneyBudget MyMoneyFile::budget(const QString& id) const { d->checkStorage(); return d->m_storage->budget(id); } void MyMoneyFile::removeBudget(const MyMoneyBudget& budget) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->removeBudget(budget); } void MyMoneyFile::addOnlineJob(onlineJob& job) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->addOnlineJob(job); d->m_changeSet += MyMoneyNotification(File::Mode::Add, job); } void MyMoneyFile::modifyOnlineJob(const onlineJob job) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifyOnlineJob(job); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, job); } onlineJob MyMoneyFile::getOnlineJob(const QString &jobId) const { d->checkStorage(); return d->m_storage->getOnlineJob(jobId); } QList MyMoneyFile::onlineJobList() const { d->checkStorage(); return d->m_storage->onlineJobList(); } /** @todo improve speed by passing count job to m_storage */ int MyMoneyFile::countOnlineJobs() const { return onlineJobList().count(); } /** * @brief Remove onlineJob * @param job onlineJob to remove */ void MyMoneyFile::removeOnlineJob(const onlineJob& job) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache if (job.isLocked()) { return; } d->m_changeSet += MyMoneyNotification(File::Mode::Remove, job); d->m_storage->removeOnlineJob(job); } void MyMoneyFile::removeOnlineJob(const QStringList onlineJobIds) { foreach (QString jobId, onlineJobIds) { removeOnlineJob(getOnlineJob(jobId)); } } void MyMoneyFile::costCenterList(QList< MyMoneyCostCenter >& list) const { d->checkStorage(); list = d->m_storage->costCenterList(); } void MyMoneyFile::updateVAT(MyMoneyTransaction& transaction) const { // check if transaction qualifies const auto splitCount = transaction.splits().count(); if (splitCount > 1 && splitCount <= 3) { MyMoneyMoney amount; MyMoneyAccount assetLiability; MyMoneyAccount category; MyMoneySplit taxSplit; const QString currencyId = transaction.commodity(); foreach (const auto& split, transaction.splits()) { const auto acc = account(split.accountId()); // all splits must reference accounts denoted in the same currency if (acc.currencyId() != currencyId) { return; } if (acc.isAssetLiability() && assetLiability.id().isEmpty()) { amount = split.shares(); assetLiability = acc; continue; } if (acc.isAssetLiability()) { return; } if (category.id().isEmpty() && !acc.value("VatAccount").isEmpty()) { category = acc; continue; } else if(taxSplit.id().isEmpty() && !acc.value("Tax").toLower().compare(QLatin1String("yes"))) { taxSplit = split; continue; } return; } if (!category.id().isEmpty()) { // remove a possibly found tax split - we create a new one // but only if it is the same tax category if (!taxSplit.id().isEmpty()) { if (category.value("VatAccount").compare(taxSplit.accountId())) return; transaction.removeSplit(taxSplit); } addVATSplit(transaction, assetLiability, category, amount); } } } bool MyMoneyFile::addVATSplit(MyMoneyTransaction& transaction, const MyMoneyAccount& acc, const MyMoneyAccount& category, const MyMoneyMoney& amount) const { bool rc = false; try { MyMoneySplit cat; // category MyMoneySplit tax; // tax if (category.value("VatAccount").isEmpty()) return false; MyMoneyAccount vatAcc = account(category.value("VatAccount")); const MyMoneySecurity& asec = security(acc.currencyId()); const MyMoneySecurity& csec = security(category.currencyId()); const MyMoneySecurity& vsec = security(vatAcc.currencyId()); if (asec.id() != csec.id() || asec.id() != vsec.id()) { qDebug("Auto VAT assignment only works if all three accounts use the same currency."); return false; } MyMoneyMoney vatRate(vatAcc.value("VatRate")); MyMoneyMoney gv, nv; // gross value, net value int fract = acc.fraction(); if (!vatRate.isZero()) { tax.setAccountId(vatAcc.id()); // qDebug("vat amount is '%s'", category.value("VatAmount").toLatin1()); if (category.value("VatAmount").toLower() != QString("net")) { // split value is the gross value gv = amount; nv = (gv / (MyMoneyMoney::ONE + vatRate)).convert(fract); MyMoneySplit catSplit = transaction.splitByAccount(acc.id(), false); catSplit.setShares(-nv); catSplit.setValue(catSplit.shares()); transaction.modifySplit(catSplit); } else { // split value is the net value nv = amount; gv = (nv * (MyMoneyMoney::ONE + vatRate)).convert(fract); MyMoneySplit accSplit = transaction.splitByAccount(acc.id()); accSplit.setValue(gv.convert(fract)); accSplit.setShares(accSplit.value()); transaction.modifySplit(accSplit); } tax.setValue(-(gv - nv).convert(fract)); tax.setShares(tax.value()); transaction.addSplit(tax); rc = true; } } catch (const MyMoneyException &) { } return rc; } bool MyMoneyFile::isReferenced(const MyMoneyObject& obj, const QBitArray& skipChecks) const { d->checkStorage(); return d->m_storage->isReferenced(obj, skipChecks); } bool MyMoneyFile::isReferenced(const MyMoneyObject& obj) const { return isReferenced(obj, QBitArray((int)eStorage::Reference::Count)); } bool MyMoneyFile::checkNoUsed(const QString& accId, const QString& no) const { // by definition, an empty string or a non-numeric string is not used QRegExp exp(QString("(.*\\D)?(\\d+)(\\D.*)?")); if (no.isEmpty() || exp.indexIn(no) == -1) return false; MyMoneyTransactionFilter filter; filter.addAccount(accId); QList transactions = transactionList(filter); QList::ConstIterator it_t = transactions.constBegin(); while (it_t != transactions.constEnd()) { try { MyMoneySplit split; // Test whether the transaction also includes a split into // this account split = (*it_t).splitByAccount(accId, true /*match*/); if (!split.number().isEmpty() && split.number() == no) return true; } catch (const MyMoneyException &) { } ++it_t; } return false; } QString MyMoneyFile::highestCheckNo(const QString& accId) const { unsigned64 lno = 0; unsigned64 cno; QString no; MyMoneyTransactionFilter filter; filter.addAccount(accId); QList transactions = transactionList(filter); QList::ConstIterator it_t = transactions.constBegin(); while (it_t != transactions.constEnd()) { try { // Test whether the transaction also includes a split into // this account MyMoneySplit split = (*it_t).splitByAccount(accId, true /*match*/); if (!split.number().isEmpty()) { // non-numerical values stored in number will return 0 in the next line cno = split.number().toULongLong(); if (cno > lno) { lno = cno; no = split.number(); } } } catch (const MyMoneyException &) { } ++it_t; } return no; } bool MyMoneyFile::hasNewerTransaction(const QString& accId, const QDate& date) const { MyMoneyTransactionFilter filter; filter.addAccount(accId); filter.setDateFilter(date.addDays(+1), QDate()); return !transactionList(filter).isEmpty(); } void MyMoneyFile::clearCache() { d->checkStorage(); d->m_balanceCache.clear(); } void MyMoneyFile::forceDataChanged() { emit dataChanged(); } bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const { auto rc = true; if (t.splitCount() == 2) { foreach (const auto split, t.splits()) { auto acc = account(split.accountId()); if (acc.isIncomeExpense()) { rc = false; break; } } } return rc; } bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const { auto ret = false; foreach (const auto split, t.splits()) { if (referencesClosedAccount(split)) { ret = true; break; } } return ret; } bool MyMoneyFile::referencesClosedAccount(const MyMoneySplit& s) const { if (s.accountId().isEmpty()) return false; try { return account(s.accountId()).isClosed(); } catch (const MyMoneyException &) { } return false; } QString MyMoneyFile::storageId() { QString id = value("kmm-id"); if (id.isEmpty()) { MyMoneyFileTransaction ft; try { QUuid uid = QUuid::createUuid(); setValue("kmm-id", uid.toString()); ft.commit(); id = uid.toString(); } catch (const MyMoneyException &) { qDebug("Unable to setup UID for new storage object"); } } return id; } QString MyMoneyFile::openingBalancesPrefix() { return i18n("Opening Balances"); } bool MyMoneyFile::hasMatchingOnlineBalance(const MyMoneyAccount& _acc) const { // get current values auto acc = account(_acc.id()); // if there's no last transaction import data we are done if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty()) return false; // otherwise, we compare the balances MyMoneyMoney balance(acc.value("lastStatementBalance")); MyMoneyMoney accBalance = this->balance(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate)); return balance == accBalance; } int MyMoneyFile::countTransactionsWithSpecificReconciliationState(const QString& accId, TransactionFilter::State state) const { MyMoneyTransactionFilter filter; filter.addAccount(accId); filter.addState((int)state); return transactionList(filter).count(); } QMap > MyMoneyFile::countTransactionsWithSpecificReconciliationState() const { QMap > result; MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); d->checkStorage(); QList list; accountList(list); for (const auto& account : list) { result[account.id()] = QVector((int)eMyMoney::Split::State::MaxReconcileState, 0); } const auto transactions = d->m_storage->transactionList(filter); for (const auto& transaction : transactions) { const auto& splits = transaction.splits(); for (const auto& split : splits) { if (!result.contains(split.accountId())) { result[split.accountId()] = QVector((int)eMyMoney::Split::State::MaxReconcileState, 0); } const auto flag = split.reconcileFlag(); switch(flag) { case eMyMoney::Split::State::NotReconciled: case eMyMoney::Split::State::Cleared: case eMyMoney::Split::State::Reconciled: case eMyMoney::Split::State::Frozen: result[split.accountId()][(int)flag]++; break; default: break; } } } return result; } /** * Make sure that the splits value has the precision of the corresponding account */ void MyMoneyFile::fixSplitPrecision(MyMoneyTransaction& t) const { auto transactionSecurity = security(t.commodity()); auto transactionFraction = transactionSecurity.smallestAccountFraction(); for (auto& split : t.splits()) { auto acc = account(split.accountId()); auto fraction = acc.fraction(); if(fraction == -1) { auto sec = security(acc.currencyId()); fraction = acc.fraction(sec); } // Don't do any rounding on a split factor if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) { split.setShares(static_cast(split.shares().convertDenominator(fraction).canonicalize())); split.setValue(static_cast(split.value().convertDenominator(transactionFraction).canonicalize())); } } } class MyMoneyFileTransactionPrivate { Q_DISABLE_COPY(MyMoneyFileTransactionPrivate) public: MyMoneyFileTransactionPrivate() : m_isNested(MyMoneyFile::instance()->hasTransaction()), m_needRollback(!m_isNested) { } public: bool m_isNested; bool m_needRollback; }; MyMoneyFileTransaction::MyMoneyFileTransaction() : d_ptr(new MyMoneyFileTransactionPrivate) { Q_D(MyMoneyFileTransaction); if (!d->m_isNested) MyMoneyFile::instance()->startTransaction(); } MyMoneyFileTransaction::~MyMoneyFileTransaction() { try { rollback(); } catch (const MyMoneyException &e) { qDebug() << e.what(); } Q_D(MyMoneyFileTransaction); delete d; } void MyMoneyFileTransaction::restart() { rollback(); Q_D(MyMoneyFileTransaction); d->m_needRollback = !d->m_isNested; if (!d->m_isNested) MyMoneyFile::instance()->startTransaction(); } void MyMoneyFileTransaction::commit() { Q_D(MyMoneyFileTransaction); if (!d->m_isNested) MyMoneyFile::instance()->commitTransaction(); d->m_needRollback = false; } void MyMoneyFileTransaction::rollback() { Q_D(MyMoneyFileTransaction); if (d->m_needRollback) MyMoneyFile::instance()->rollbackTransaction(); d->m_needRollback = false; } diff --git a/kmymoney/plugins/csv/import/investmentwizardpage.cpp b/kmymoney/plugins/csv/import/investmentwizardpage.cpp index 537284f82..b0ef37cfe 100644 --- a/kmymoney/plugins/csv/import/investmentwizardpage.cpp +++ b/kmymoney/plugins/csv/import/investmentwizardpage.cpp @@ -1,665 +1,665 @@ /* * Copyright 2010-2017 Allan Anderson * Copyright 2016-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "investmentwizardpage.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "csvwizard.h" #include "core/csvimportercore.h" #include "transactiondlg.h" #include "securitydlg.h" #include "securitiesdlg.h" #include "ui_investmentwizardpage.h" #include "ui_securitiesdlg.h" #include "ui_securitydlg.h" // ---------------------------------------------------------------------------- InvestmentPage::InvestmentPage(CSVWizard *dlg, CSVImporterCore *imp) : CSVWizardPage(dlg, imp), ui(new Ui::InvestmentPage) { ui->setupUi(this); connect(ui->m_clear, &QAbstractButton::clicked, this, &InvestmentPage::clearColumns); connect(ui->m_clearFee, &QAbstractButton::clicked, this, &InvestmentPage::clearFee); connect(ui->m_calculateFee, &QAbstractButton::clicked, this, &InvestmentPage::calculateFee); // initialize column names m_dlg->m_colTypeName.insert(Column::Type, i18n("Type")); m_dlg->m_colTypeName.insert(Column::Price, i18n("Price")); m_dlg->m_colTypeName.insert(Column::Quantity, i18n("Quantity")); m_dlg->m_colTypeName.insert(Column::Fee, i18n("Fee")); m_dlg->m_colTypeName.insert(Column::Date, i18n("Date")); m_dlg->m_colTypeName.insert(Column::Amount, i18n("Amount")); - m_dlg->m_colTypeName.insert(Column::Symbol, i18n("Symbol")); + m_dlg->m_colTypeName.insert(Column::Symbol, i18nc("@title stock symbol column", "Symbol")); m_dlg->m_colTypeName.insert(Column::Name, i18n("Name")); m_dlg->m_colTypeName.insert(Column::Memo, i18n("Memo")); m_profile = dynamic_cast(m_imp->m_profile); connect(ui->m_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(memoColSelected(int))); connect(ui->m_typeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(typeColSelected(int))); connect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int))); connect(ui->m_quantityCol, SIGNAL(currentIndexChanged(int)), this, SLOT(quantityColSelected(int))); connect(ui->m_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(priceColSelected(int))); connect(ui->m_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(fractionChanged(int))); connect(ui->m_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(amountColSelected(int))); connect(ui->m_feeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(feeColSelected(int))); connect(ui->m_symbolCol, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolColSelected(int))); connect(ui->m_nameCol, SIGNAL(currentIndexChanged(int)), this, SLOT(nameColSelected(int))); connect(ui->m_feeIsPercentage, &QAbstractButton::clicked, this, &InvestmentPage::feeIsPercentageClicked); connect(ui->m_feeRate, &QLineEdit::editingFinished, this, &InvestmentPage::feeInputsChanged); connect(ui->m_feeRate, &QLineEdit::textChanged, this, &InvestmentPage::feeRateChanged); connect(ui->m_minFee, &QLineEdit::textChanged, this, &InvestmentPage::minFeeChanged); } InvestmentPage::~InvestmentPage() { delete m_securitiesDlg; delete ui; } void InvestmentPage::calculateFee() { m_imp->calculateFee(); m_dlg->updateWindowSize(); m_dlg->markUnwantedRows(); } void InvestmentPage::initializePage() { QHash columns {{Column::Amount, ui->m_amountCol}, {Column::Type, ui->m_typeCol}, {Column::Quantity, ui->m_quantityCol}, {Column::Memo, ui->m_memoCol}, {Column::Price, ui->m_priceCol}, {Column::Date, ui->m_dateCol}, {Column::Fee, ui->m_feeCol}, {Column::Symbol, ui->m_symbolCol}, {Column::Name, ui->m_nameCol}}; if (ui->m_dateCol->count() != m_imp->m_file->m_columnCount) m_dlg->initializeComboBoxes(columns); ui->m_feeIsPercentage->setChecked(m_profile->m_feeIsPercentage); columns.remove(Column::Memo); for (auto it = columns.cbegin(); it != columns.cend(); ++it) it.value()->setCurrentIndex(m_profile->m_colTypeNum.value(it.key())); ui->m_priceFraction->blockSignals(true); foreach (const auto priceFraction, m_imp->m_priceFractions) ui->m_priceFraction->addItem(QString::number(priceFraction.toDouble(), 'g', 3)); ui->m_priceFraction->blockSignals(false); ui->m_priceFraction->setCurrentIndex(m_profile->m_priceFraction); ui->m_feeRate->setText(m_profile->m_feeRate); ui->m_minFee->setText(m_profile->m_minFee); ui->m_feeRate->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,2}[") + QLocale().decimalPoint() + QStringLiteral("]{1,1}[0-9]{0,2}")), this) ); ui->m_minFee->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,}[") + QLocale().decimalPoint() + QStringLiteral("]{0,1}[0-9]{0,}")), this) ); if (!m_profile->m_feeRate.isEmpty()) { // fee rate indicates that fee column needs to be calculated if (m_imp->calculateFee()) { ui->m_feeCol->blockSignals(true); int feeCol = ui->m_feeCol->count(); ui->m_feeCol->addItem(QString::number(feeCol + 1)); ui->m_feeCol->setEnabled(false); ui->m_feeCol->setCurrentIndex(feeCol); ui->m_feeCol->blockSignals(false); m_dlg->updateWindowSize(); m_dlg->markUnwantedRows(); } } else if (m_profile->m_colTypeNum.value(Column::Fee, -1) >= ui->m_feeCol->count()) { // no fee rate, calculated fee column index exist, but the column doesn't exist and that's not ok m_profile->m_colTypeNum[Column::Fee] = -1; m_profile->m_colNumType.remove(m_profile->m_colNumType.key(Column::Fee)); } for (int i = 0; i < m_profile->m_memoColList.count(); ++i) { // Set up all memo fields... int tmp = m_profile->m_memoColList.at(i); if (tmp < m_profile->m_colTypeNum.count()) ui->m_memoCol->setCurrentIndex(tmp); } feeInputsChanged(); } bool InvestmentPage::isComplete() const { return ui->m_dateCol->currentIndex() > -1 && ui->m_typeCol->currentIndex() > -1 && ui->m_quantityCol->currentIndex() > -1 && ui->m_priceCol->currentIndex() > -1 && ui->m_amountCol->currentIndex() > -1 && ui->m_priceFraction->currentIndex() > -1; } bool InvestmentPage::validatePage() { if (ui->m_symbolCol->currentIndex() == -1 && ui->m_nameCol->currentIndex() == -1) return validateSecurity(); else return validateSecurities(); } void InvestmentPage::cleanupPage() { clearFeeCol(); } void InvestmentPage::memoColSelected(int col) { if (m_profile->m_colNumType.value(col) == Column::Type || m_profile->m_colNumType.value(col) == Column::Name) { int rc = KMessageBox::Yes; if (isVisible()) rc = KMessageBox::questionYesNo(m_dlg, i18n("
The '%1' field already has this column selected.
" "
If you wish to copy that data to the memo field, click 'Yes'.
", m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col)))); if (rc == KMessageBox::Yes) { ui->m_memoCol->setItemText(col, QString::number(col + 1) + QChar(QLatin1Char('*'))); if (!m_profile->m_memoColList.contains(col)) m_profile->m_memoColList.append(col); } else { ui->m_memoCol->setItemText(col, QString::number(col + 1)); m_profile->m_memoColList.removeOne(col); } //allow only separate memo field occupy combobox ui->m_memoCol->blockSignals(true); if (m_profile->m_colTypeNum.value(Column::Memo) != -1) ui->m_memoCol->setCurrentIndex(m_profile->m_colTypeNum.value(Column::Memo)); else ui->m_memoCol->setCurrentIndex(-1); ui->m_memoCol->blockSignals(false); return; } if (m_profile->m_colTypeNum.value(Column::Memo) != -1) // check if this memo has any column 'number' assigned... m_profile->m_memoColList.removeOne(col); // ...if true remove it from memo list if(validateSelectedColumn(col, Column::Memo)) if (col != - 1 && !m_profile->m_memoColList.contains(col)) m_profile->m_memoColList.append(col); } void InvestmentPage::dateColSelected(int col) { validateSelectedColumn(col, Column::Date); } void InvestmentPage::feeColSelected(int col) { validateSelectedColumn(col, Column::Fee); feeInputsChanged(); } void InvestmentPage::typeColSelected(int col) { if (validateSelectedColumn(col, Column::Type)) if (!validateMemoComboBox()) // user could have it already in memo so... memoColSelected(col); // ...if true set memo field again } void InvestmentPage::quantityColSelected(int col) { validateSelectedColumn(col, Column::Quantity); } void InvestmentPage::priceColSelected(int col) { validateSelectedColumn(col, Column::Price); } void InvestmentPage::amountColSelected(int col) { validateSelectedColumn(col, Column::Amount); clearFeeCol(); feeInputsChanged(); } void InvestmentPage::symbolColSelected(int col) { validateSelectedColumn(col, Column::Symbol); m_imp->m_mapSymbolName.clear(); // new symbol column so this map is no longer valid } void InvestmentPage::nameColSelected(int col) { if (validateSelectedColumn(col, Column::Name)) if (!validateMemoComboBox()) // user could have it already in memo so... memoColSelected(col); // ...if true set memo field again m_imp->m_mapSymbolName.clear(); // new name column so this map is no longer valid } void InvestmentPage::feeIsPercentageClicked(bool checked) { m_profile->m_feeIsPercentage = checked; } void InvestmentPage::fractionChanged(int col) { m_profile->m_priceFraction = col; emit completeChanged(); } void InvestmentPage::clearColumns() { ui->m_amountCol->setCurrentIndex(-1); ui->m_dateCol->setCurrentIndex(-1); ui->m_priceCol->setCurrentIndex(-1); ui->m_quantityCol->setCurrentIndex(-1); ui->m_memoCol->setCurrentIndex(-1); ui->m_typeCol->setCurrentIndex(-1); ui->m_nameCol->setCurrentIndex(-1); ui->m_symbolCol->setCurrentIndex(-1); ui->m_priceFraction->setCurrentIndex(-1); clearFee(); } void InvestmentPage::clearFee() { clearFeeCol(); ui->m_feeCol->setCurrentIndex(-1); ui->m_feeIsPercentage->setChecked(false); ui->m_calculateFee->setEnabled(false); ui->m_feeRate->setEnabled(true); ui->m_minFee->setEnabled(false); ui->m_feeRate->clear(); ui->m_minFee->clear(); } void InvestmentPage::feeInputsChanged() { // if (ui->comboBoxInv_feeCol->currentIndex() < m_importer->m_file->m_columnCount && // ui->comboBoxInv_feeCol->currentIndex() > -1) { // ui->lineEdit_minFee->setEnabled(false); // ui->lineEdit_feeRate->setEnabled(false); // ui->lineEdit_minFee->clear(); // ui->lineEdit_feeRate->clear(); // } if (m_profile->m_feeRate.isEmpty()) { ui->m_feeCol->setEnabled(true); ui->m_feeIsPercentage->setEnabled(true); ui->m_minFee->setEnabled(false); ui->m_calculateFee->setEnabled(false); } else { ui->m_feeCol->setEnabled(false); ui->m_feeIsPercentage->setEnabled(false); ui->m_feeIsPercentage->setChecked(true); ui->m_minFee->setEnabled(true); ui->m_feeRate->setEnabled(true); if (m_profile->m_colTypeNum.value(Column::Amount) != -1) ui->m_calculateFee->setEnabled(true); } } void InvestmentPage::feeRateChanged(const QString &text) { m_profile->m_feeRate = text; } void InvestmentPage::minFeeChanged(const QString &text) { m_profile->m_minFee = text; } void InvestmentPage::clearFeeCol() { if (!m_profile->m_feeRate.isEmpty() && // if fee rate isn't empty... m_profile->m_colTypeNum.value(Column::Fee) >= m_imp->m_file->m_columnCount - 1 && !ui->m_feeCol->isEnabled()) { // ...and fee column is last... --m_imp->m_file->m_columnCount; m_imp->m_file->m_model->removeColumn(m_imp->m_file->m_columnCount); int feeCol = ui->m_feeCol->currentIndex(); ui->m_feeCol->setCurrentIndex(-1); ui->m_feeCol->removeItem(feeCol); m_dlg->updateWindowSize(); } ui->m_feeCol->setEnabled(true); ui->m_feeIsPercentage->setEnabled(true); ui->m_feeIsPercentage->setChecked(false); } void InvestmentPage::resetComboBox(const Column comboBox) { switch (comboBox) { case Column::Amount: ui->m_amountCol->setCurrentIndex(-1); break; case Column::Date: ui->m_dateCol->setCurrentIndex(-1); break; case Column::Fee: ui->m_feeCol->setCurrentIndex(-1); break; case Column::Memo: ui->m_memoCol->setCurrentIndex(-1); break; case Column::Price: ui->m_priceCol->setCurrentIndex(-1); break; case Column::Quantity: ui->m_quantityCol->setCurrentIndex(-1); break; case Column::Type: ui->m_typeCol->setCurrentIndex(-1); break; case Column::Symbol: ui->m_symbolCol->setCurrentIndex(-1); break; case Column::Name: ui->m_nameCol->setCurrentIndex(-1); break; default: KMessageBox::sorry(m_dlg, i18n("
Field name not recognised.
'%1'
Please re-enter your column selections.", (int)comboBox), i18n("CSV import")); } } bool InvestmentPage::validateActionType() { for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) { MyMoneyStatement::Transaction tr; // process quantity field int col = m_profile->m_colTypeNum.value(Column::Quantity); tr.m_shares = m_imp->processQuantityField(m_profile, row, col); // process price field col = m_profile->m_colTypeNum.value(Column::Price); tr.m_price = m_imp->processPriceField(m_profile, row, col); // process amount field col = m_profile->m_colTypeNum.value(Column::Amount); tr.m_amount = m_imp->processAmountField(m_profile, row, col); // process type field col = m_profile->m_colTypeNum.value(Column::Type); tr.m_eAction = m_imp->processActionTypeField(m_profile, row, col); switch(m_imp->validateActionType(tr)) { case InvalidActionValues: KMessageBox::sorry(m_dlg, i18n("The values in the columns you have selected\ndo not match any expected investment type.\n" "Please check the fields in the current transaction,\nand also your selections."), i18n("CSV import")); return false; break; case NoActionType: { bool unknownType = false; if (tr.m_eAction == eMyMoney::Transaction::Action::None) unknownType = true; QStringList colList; QStringList colHeaders; for (col = 0; col < m_imp->m_file->m_columnCount; ++col) { colHeaders.append(m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col, Column::Invalid), QString(i18nc("Unused column", "Unused")))); colList.append(m_imp->m_file->m_model->item(row, col)->text()); } QList validActionTypes = m_imp->createValidActionTypes(tr); QPointer transactionDlg = new TransactionDlg(colList, colHeaders, m_profile->m_colTypeNum.value(Column::Type), validActionTypes); if (transactionDlg->exec() == QDialog::Rejected) { KMessageBox::information(m_dlg, i18n("
No valid action type found for this transaction.
" "
Please check the parameters supplied.
"), i18n("CSV import")); return false; } else tr.m_eAction = transactionDlg->getActionType(); delete transactionDlg; if (unknownType) { // type was unknown so store it col = m_profile->m_colTypeNum.value(Column::Type); m_profile->m_transactionNames[tr.m_eAction].append(m_imp->m_file->m_model->item(row, col)->text()); // store action type } } default: break; } } m_imp->m_isActionTypeValidated = true; return true; } bool InvestmentPage::validateSelectedColumn(const int col, const Column type) { if (m_profile->m_colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned... m_profile->m_colNumType.remove(m_profile->m_colTypeNum.value(type)); // ...if true remove 'type' assigned to this column 'number' bool ret = true; if (col == -1) { // user only wanted to reset his column so allow him m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type' } else if (m_profile->m_colNumType.contains(col)) { // if this column 'number' has already 'type' assigned KMessageBox::information(m_dlg, i18n("The '%1' field already has this column selected.
Please reselect both entries as necessary.
", m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col)))); resetComboBox(m_profile->m_colNumType[col]); resetComboBox(type); ret = false; } else { m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type' m_profile->m_colNumType[col] = type; // assign new 'type' to this column 'number' } emit completeChanged(); return ret; } bool InvestmentPage::validateMemoComboBox() { if (m_profile->m_memoColList.count() == 0) return true; for (int i = 0; i < ui->m_memoCol->count(); ++i) { QString txt = ui->m_memoCol->itemText(i); if (txt.contains(QLatin1Char('*'))) // check if text containing '*' belongs to valid column types if (m_profile->m_colNumType.value(i) != Column::Name && m_profile->m_colNumType.value(i) != Column::Type) { ui->m_memoCol->setItemText(i, QString::number(i + 1)); m_profile->m_memoColList.removeOne(i); return false; } } return true; } bool InvestmentPage::validateSecurities() { if (m_securitiesDlg.isNull() && m_imp->m_mapSymbolName.isEmpty()) { QSet onlySymbols; QSet onlyNames; m_imp->sortSecurities(onlySymbols, onlyNames, m_imp->m_mapSymbolName); if (!onlySymbols.isEmpty() || !onlyNames.isEmpty()) { m_securitiesDlg = new SecuritiesDlg; for (QSet::const_iterator symbol = onlySymbols.cbegin(); symbol != onlySymbols.cend(); ++symbol) m_securitiesDlg->displayLine(*symbol, QString()); for (QSet::const_iterator name = onlyNames.cbegin(); name != onlyNames.cend(); ++name) m_securitiesDlg->displayLine(QString(), *name); } } if (!m_securitiesDlg.isNull()) { QTableWidget* symbolTable = m_securitiesDlg->ui->tableWidget; if (m_securitiesDlg->exec() == QDialog::Rejected) { return false; } else { for (int row = 0; row < symbolTable->rowCount(); ++row) { QString symbol = symbolTable->item(row, 1)->text(); QString name = symbolTable->item(row, 2)->text(); m_imp->m_mapSymbolName.insert(symbol, name); } delete m_securitiesDlg; } } return true; } bool InvestmentPage::validateSecurity() { if (!m_profile->m_securitySymbol.isEmpty() && !m_profile->m_securityName.isEmpty()) m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); MyMoneyFile* file = MyMoneyFile::instance(); if (m_securityDlg.isNull() && (m_imp->m_mapSymbolName.isEmpty() || !(m_profile->m_dontAsk && m_dlg->m_skipSetup))) { m_securityDlg = new SecurityDlg; m_securityDlg->initializeSecurities(m_profile->m_securitySymbol, m_profile->m_securityName); m_securityDlg->ui->cbDontAsk->setChecked(m_profile->m_dontAsk); } if (!m_securityDlg.isNull()) { if (m_securityDlg->exec() == QDialog::Rejected) { return false; } else { QString securityID = m_securityDlg->security(); if (!securityID.isEmpty()) { m_profile->m_securityName = file->security(securityID).name(); m_profile->m_securitySymbol = file->security(securityID).tradingSymbol(); } else { m_profile->m_securityName = m_securityDlg->name(); m_profile->m_securitySymbol = m_securityDlg->symbol(); } m_profile->m_dontAsk = m_securityDlg->dontAsk(); m_imp->m_mapSymbolName.clear(); m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); // probably we should check if security with given symbol and name exists... delete m_securityDlg; // ...but KMM allows creating duplicates, so don't bother } } if (m_imp->m_mapSymbolName.isEmpty()) return false; return true; } void InvestmentPage::makeQIF(const MyMoneyStatement& st, const QString& outFileName) { QFile oFile(outFileName); oFile.open(QIODevice::WriteOnly); QTextStream out(&oFile); QString buffer; QString strEType; switch (st.m_eType) { case eMyMoney::Statement::Type::Investment: default: strEType = QStringLiteral("Invst"); } if (!st.m_strAccountName.isEmpty()) { buffer.append(QStringLiteral("!Account\n")); buffer.append(QChar(QLatin1Char('N')) + st.m_strAccountName + QChar(QLatin1Char('\n'))); buffer.append(QChar(QLatin1Char('T')) + strEType + QChar(QLatin1Char('\n'))); buffer.append(QStringLiteral("^\n")); } for (QList::const_iterator it = st.m_listSecurities.constBegin(); it != st.m_listSecurities.constEnd(); ++it) { buffer.append(QStringLiteral("!Type:Security\n")); buffer.append(QChar(QLatin1Char('N')) + (*it).m_strName + QChar(QLatin1Char('\n'))); buffer.append(QChar(QLatin1Char('S')) + (*it).m_strSymbol + QChar(QLatin1Char('\n'))); buffer.append(QStringLiteral("TStock\n^\n")); } if (!st.m_strAccountName.isEmpty()) { buffer.append(QStringLiteral("!Account\n")); buffer.append(QChar(QLatin1Char('N')) + st.m_strAccountName + QChar(QLatin1Char('\n'))); buffer.append(QChar(QLatin1Char('T')) + strEType + QChar(QLatin1Char('\n'))); buffer.append(QStringLiteral("^\n")); } buffer.append(QStringLiteral("!Type:") + strEType + QChar(QLatin1Char('\n'))); for (QList::const_iterator it = st.m_listTransactions.constBegin(); it != st.m_listTransactions.constEnd(); ++it) { buffer.append(QChar(QLatin1Char('D')) + it->m_datePosted.toString(QStringLiteral("MM/dd/yyyy")) + QChar(QLatin1Char('\n'))); buffer.append(QChar(QLatin1Char('Y')) + it->m_strSecurity + QChar(QLatin1Char('\n'))); QString txt; switch (it->m_eAction) { case eMyMoney::Transaction::Action::Buy: txt = QStringLiteral("Buy"); break; case eMyMoney::Transaction::Action::Sell: txt = QStringLiteral("Sell"); break; case eMyMoney::Transaction::Action::ReinvestDividend: txt = QStringLiteral("ReinvDiv"); break; case eMyMoney::Transaction::Action::CashDividend: txt = QStringLiteral("Div"); break; case eMyMoney::Transaction::Action::Interest: txt = QStringLiteral("IntInc"); break; case eMyMoney::Transaction::Action::Shrsin: txt = QStringLiteral("ShrsIn"); break; case eMyMoney::Transaction::Action::Shrsout: txt = QStringLiteral("ShrsOut"); break; case eMyMoney::Transaction::Action::Stksplit: txt = QStringLiteral("stksplit"); break; default: txt = QStringLiteral("unknown"); // shouldn't happen } buffer.append(QChar(QLatin1Char('N')) + txt + QChar(QLatin1Char('\n'))); if (it->m_eAction == eMyMoney::Transaction::Action::Buy) // we added 'N' field so buy transaction should have any sign txt.setNum(it->m_amount.abs().toDouble(), 'f', 4); else txt.setNum(it->m_amount.toDouble(), 'f', 4); buffer.append(QChar(QLatin1Char('T')) + txt + QChar(QLatin1Char('\n'))); txt.setNum(it->m_shares.toDouble(), 'f', 4); buffer.append(QChar(QLatin1Char('Q')) + txt + QChar(QLatin1Char('\n'))); txt.setNum(it->m_price.toDouble(), 'f', 4); buffer.append(QChar(QLatin1Char('I')) + txt + QChar(QLatin1Char('\n'))); if (!it->m_fees.isZero()) { txt.setNum(it->m_fees.toDouble(), 'f', 4); buffer.append(QChar(QLatin1Char('O')) + txt + QChar(QLatin1Char('\n'))); } if (!it->m_strBrokerageAccount.isEmpty()) buffer.append(QChar(QLatin1Char('L')) + it->m_strBrokerageAccount + QChar(QLatin1Char('\n'))); if (!it->m_strMemo.isEmpty()) buffer.append(QChar(QLatin1Char('M')) + it->m_strMemo + QChar(QLatin1Char('\n'))); buffer.append(QStringLiteral("^\n")); out << buffer;// output qif file buffer.clear(); } oFile.close(); } diff --git a/kmymoney/plugins/csv/import/investmentwizardpage.ui b/kmymoney/plugins/csv/import/investmentwizardpage.ui index 4f2edca46..b52b3b1dc 100644 --- a/kmymoney/plugins/csv/import/investmentwizardpage.ui +++ b/kmymoney/plugins/csv/import/investmentwizardpage.ui @@ -1,564 +1,564 @@ InvestmentPage 0 0 846 284 0 0 Investment Wizard Page Qt::Horizontal 40 20 0 0 50 false Please select the appropriate columns to use, corresponding to your data. If there is just a single security in the file, enter its name below. Otherwise, select the column for the symbol, and for the description or name. Qt::AlignCenter true 0 0 Qt::Horizontal Memo Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Quantity Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Select column containing amount field. 12 0 0 If necessary, select a fraction/multiplier for compatibility between imported and stored prices. For instance, if the import price is in cents but the file is priced in dollars, select 0.01. 5 Select column containing date field Date Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true 16777215 16777215 Price Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Select column containing date field. 12 true Amount Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Select column containing quantity field. 12 0 0 Select column containing price field. 12 0 0 If more than one stock is present in the file, and if the stock symbols are shown in one of the columns, select it here. 0 0 Clear selected column entries. Clear 0 0 Qt::Horizontal 16777215 16777215 Price Fraction Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - Symbol + Symbol Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 If a descriptive column also contains an activity type, select it here. See also Filter text field. Name Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Fee is Percentage Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LeftToRight Fee Column Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Check box if any fee is a percentage figure rather than a value. Qt::RightToLeft 0 0 Select column containing investment type field. 12 0 0 If necessary, select column containing fee. Caution. This might already have been incorporated into the price. 12 0 0 Fee rate Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Type/Action Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Select column containing memo field. More than one column may be entered successively. May also be selected to contain a copy of the type or name column. 12 Minimal Fee Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Qt::Horizontal 0 0 Clear Fee 0 0 Calculate Fee Qt::Horizontal 40 20 m_dateCol m_typeCol m_memoCol m_priceCol m_quantityCol m_amountCol m_priceFraction m_symbolCol m_nameCol m_feeCol m_feeIsPercentage m_feeRate m_minFee m_calculateFee m_clear m_clearFee diff --git a/kmymoney/plugins/csv/import/securitiesdlg.ui b/kmymoney/plugins/csv/import/securitiesdlg.ui index 0800a75d9..ea7fbc785 100644 --- a/kmymoney/plugins/csv/import/securitiesdlg.ui +++ b/kmymoney/plugins/csv/import/securitiesdlg.ui @@ -1,158 +1,158 @@ SecuritiesDlg 0 0 606 306 Securities 75 true Missing names and symbols Qt::AlignCenter The following securities' names and symbols are not known to KMyMoney. Please complete every missing name or symbol. Qt::AlignCenter true QAbstractItemView::NoSelection false 3 true true false Status - Symbol + Symbol Name 0 0 QDialogButtonBox::Cancel|QDialogButtonBox::Ok true buttonBox accepted() SecuritiesDlg accept() 302 280 302 152 buttonBox rejected() SecuritiesDlg reject() 302 280 302 152 2 2 true true true diff --git a/kmymoney/widgets/kmymoneycashflowcombo.cpp b/kmymoney/widgets/kmymoneycashflowcombo.cpp index cbb7ba5ee..bb4ce69d3 100644 --- a/kmymoney/widgets/kmymoneycashflowcombo.cpp +++ b/kmymoney/widgets/kmymoneycashflowcombo.cpp @@ -1,106 +1,106 @@ /* * Copyright 2009-2010 Cristian Oneț * Copyright 2009-2010 Alvaro Soliverez * Copyright 2011-2017 Thomas Baumgart * Copyright 2017 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kmymoneycashflowcombo.h" #include "kmymoneymvccombo_p.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyenums.h" #include "widgetenums.h" using namespace eWidgets; using namespace eMyMoney; class KMyMoneyCashFlowComboPrivate : public KMyMoneyMVCComboPrivate { Q_DISABLE_COPY(KMyMoneyCashFlowComboPrivate) public: KMyMoneyCashFlowComboPrivate() : m_dir(eRegister::CashFlowDirection::Unknown) { } eRegister::CashFlowDirection m_dir; }; KMyMoneyCashFlowCombo::KMyMoneyCashFlowCombo(Account::Type accountType, QWidget* parent) : KMyMoneyMVCCombo(*new KMyMoneyCashFlowComboPrivate, false, parent) { addItem(" ", QVariant((int)eRegister::CashFlowDirection::Unknown)); if (accountType == Account::Type::Income || accountType == Account::Type::Expense) { // this is used for income/expense accounts to just show the reverse sense addItem(i18nc("Activity for income categories", "Received"), QVariant((int)eRegister::CashFlowDirection::Payment)); addItem(i18nc("Activity for expense categories", "Paid"), QVariant((int)eRegister::CashFlowDirection::Deposit)); } else { - addItem(i18n("Pay to"), QVariant((int)eRegister::CashFlowDirection::Payment)); - addItem(i18n("From"), QVariant((int)eRegister::CashFlowDirection::Deposit)); + addItem(i18nc("Payee", "Pay to"), QVariant((int)eRegister::CashFlowDirection::Payment)); + addItem(i18nc("Payer", "From"), QVariant((int)eRegister::CashFlowDirection::Deposit)); } connect(this, &KMyMoneyMVCCombo::itemSelected, this, &KMyMoneyCashFlowCombo::slotSetDirection); } KMyMoneyCashFlowCombo::~KMyMoneyCashFlowCombo() { } void KMyMoneyCashFlowCombo::setDirection(eRegister::CashFlowDirection dir) { Q_D(KMyMoneyCashFlowCombo); d->m_dir = dir; QString num; setSelectedItem(num.setNum((int)dir)); } eRegister::CashFlowDirection KMyMoneyCashFlowCombo::direction() const { Q_D(const KMyMoneyCashFlowCombo); return d->m_dir; } void KMyMoneyCashFlowCombo::slotSetDirection(const QString& id) { Q_D(KMyMoneyCashFlowCombo); QString num; for (int i = (int)eRegister::CashFlowDirection::Deposit; i <= (int)eRegister::CashFlowDirection::Unknown; ++i) { num.setNum(i); if (num == id) { d->m_dir = static_cast(i); break; } } emit directionSelected(d->m_dir); update(); } void KMyMoneyCashFlowCombo::removeDontCare() { removeItem(findData(QVariant((int)eRegister::CashFlowDirection::Unknown), Qt::UserRole, Qt::MatchExactly)); } diff --git a/kmymoney/widgets/transaction_p.h b/kmymoney/widgets/transaction_p.h index af3f671de..759c8aa7c 100644 --- a/kmymoney/widgets/transaction_p.h +++ b/kmymoney/widgets/transaction_p.h @@ -1,135 +1,135 @@ /* * Copyright 2006-2018 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef TRANSACTION_P_H #define TRANSACTION_P_H #include "registeritem_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "register.h" #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "mymoneymoney.h" #include "mymoneypayee.h" #include "mymoneysplit.h" #include "mymoneytag.h" #include "mymoneytransaction.h" namespace KMyMoneyRegister { class TransactionPrivate : public RegisterItemPrivate { public: TransactionPrivate() : m_form(nullptr), m_formRowHeight(-1), m_selected(false), m_focus(false), m_erroneous(false), m_inEdit(false), m_inRegisterEdit(false), m_showBalance(true), m_reducedIntensity(false) { } virtual ~ TransactionPrivate() { } void init(int uniqueId) { auto file = MyMoneyFile::instance(); // load the account if (!m_split.accountId().isEmpty()) m_account = file->account(m_split.accountId()); // load the payee if (!m_split.payeeId().isEmpty()) { m_payee = file->payee(m_split.payeeId()).name(); } if (m_parent->account().isIncomeExpense()) { - m_payeeHeader = m_split.shares().isNegative() ? i18n("From") : i18n("Pay to"); + m_payeeHeader = m_split.shares().isNegative() ? i18nc("Payer", "From") : i18nc("Payee", "Pay to"); } else { - m_payeeHeader = m_split.shares().isNegative() ? i18n("Pay to") : i18n("From"); + m_payeeHeader = m_split.shares().isNegative() ? i18nc("Payee", "Pay to") : i18nc("Payer", "From"); } // load the tag if (!m_split.tagIdList().isEmpty()) { const QList t = m_split.tagIdList(); for (auto i = 0; i < t.count(); i++) { m_tagList << file->tag(t[i]).name(); m_tagColorList << file->tag(t[i]).tagColor(); } } // load the currency if (!m_transaction.id().isEmpty()) m_splitCurrencyId = m_account.currencyId(); // check if transaction is erroneous or not m_erroneous = !m_transaction.splitSum().isZero(); if (!m_uniqueId.isEmpty()) { m_uniqueId += '-'; QString id; id.setNum(uniqueId); m_uniqueId += id.rightJustified(3, '0'); } } MyMoneyTransaction m_transaction; MyMoneySplit m_split; MyMoneyAccount m_account; MyMoneyMoney m_balance; QTableWidget* m_form; QString m_category; QString m_payee; QString m_payeeHeader; QList m_tagList; QList m_tagColorList; QString m_categoryHeader; QString m_splitCurrencyId; QString m_uniqueId; int m_formRowHeight; bool m_selected; bool m_focus; bool m_erroneous; bool m_inEdit; bool m_inRegisterEdit; bool m_showBalance; bool m_reducedIntensity; }; } #endif