diff --git a/kmymoney/converter/existingtransactionmatchfinder.h b/kmymoney/converter/existingtransactionmatchfinder.h index 69a293c52..72de96e15 100644 --- a/kmymoney/converter/existingtransactionmatchfinder.h +++ b/kmymoney/converter/existingtransactionmatchfinder.h @@ -1,52 +1,52 @@ /*************************************************************************** KMyMoney transaction importing module - searches for a matching transaction in the ledger copyright : (C) 2012 by Lukasz Maszczynski ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef EXISTINGTRANSACTIONMATCHFINDER_H #define EXISTINGTRANSACTIONMATCHFINDER_H #include #include #include "transactionmatchfinder.h" /** Implements searching for a matching transaction in the ledger */ class ExistingTransactionMatchFinder : public TransactionMatchFinder { public: /** Ctor, initializes the match finder * @param matchWindow max number of days the transactions may vary and still be considered to be matching */ - ExistingTransactionMatchFinder(int matchWindow = 3); + explicit ExistingTransactionMatchFinder(int matchWindow = 3); protected: typedef QPair TransactionAndSplitPair; QList listOfMatchCandidates; /** Creates a list of transactions within matchWindow range and with the same amount as the imported transaction we're trying to match */ virtual void createListOfMatchCandidates(); /** Searches for a matching transaction in the ledger * * Match result can be set to any value described in @ref TransactionMatchFinder::findMatch(). * @ref MatchDuplicate is set if the imported transaction has the same bank id as the existing transaction; * @ref MatchImprecise is set when transaction dates are not equal, but within matchWindow range */ virtual void findMatchInMatchCandidatesList(); }; #endif // EXISTINGTRANSACTIONMATCHFINDER_H diff --git a/kmymoney/converter/mymoneystatementreader.cpp b/kmymoney/converter/mymoneystatementreader.cpp index a52dbe905..3ccc1235d 100644 --- a/kmymoney/converter/mymoneystatementreader.cpp +++ b/kmymoney/converter/mymoneystatementreader.cpp @@ -1,1549 +1,1549 @@ /*************************************************************************** mymoneystatementreader.cpp ------------------- begin : Mon Aug 30 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jones ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "mymoneystatementreader.h" #include // ---------------------------------------------------------------------------- // QT Headers #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Headers #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneyprice.h" #include "mymoneytransactionfilter.h" #include "mymoneypayee.h" #include "mymoneystatement.h" #include "kmymoneyglobalsettings.h" #include "transactioneditor.h" #include "stdtransactioneditor.h" #include "kmymoneyedit.h" #include "kaccountselectdlg.h" #include "transactionmatcher.h" #include "kenterscheduledlg.h" #include "kmymoney.h" #include "kmymoneyaccountcombo.h" #include "accountsmodel.h" #include "models.h" #include "existingtransactionmatchfinder.h" #include "scheduledtransactionmatchfinder.h" #include "dialogenums.h" #include "mymoneyenums.h" #include "modelenums.h" using namespace eMyMoney; bool matchNotEmpty(const QString &l, const QString &r) { return !l.isEmpty() && QString::compare(l, r, Qt::CaseInsensitive) == 0; } class MyMoneyStatementReader::Private { public: Private() : transactionsCount(0), transactionsAdded(0), transactionsMatched(0), transactionsDuplicate(0), scannedCategories(false) {} const QString& feeId(const MyMoneyAccount& invAcc); const QString& interestId(const MyMoneyAccount& invAcc); QString interestId(const QString& name); QString expenseId(const QString& name); QString feeId(const QString& name); void assignUniqueBankID(MyMoneySplit& s, const MyMoneyStatement::Transaction& t_in); void setupPrice(MyMoneySplit &s, const MyMoneyAccount &splitAccount, const MyMoneyAccount &transactionAccount, const QDate &postDate); MyMoneyAccount lastAccount; MyMoneyAccount m_account; MyMoneyAccount m_brokerageAccount; QList transactions; QList payees; int transactionsCount; int transactionsAdded; int transactionsMatched; int transactionsDuplicate; QMap uniqIds; QMap securitiesBySymbol; QMap securitiesByName; bool m_skipCategoryMatching; private: void scanCategories(QString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const QString& defaultName); /** * This method tries to figure out the category to be used for fees and interest * from previous transactions in the given @a investmentAccount and returns the * ids of those categories in @a feesId and @a interestId. The last used category * will be returned. */ void previouslyUsedCategories(const QString& investmentAccount, QString& feesId, QString& interestId); QString nameToId(const QString&name, MyMoneyAccount& parent); private: QString m_feeId; QString m_interestId; bool scannedCategories; }; const QString& MyMoneyStatementReader::Private::feeId(const MyMoneyAccount& invAcc) { scanCategories(m_feeId, invAcc, MyMoneyFile::instance()->expense(), i18n("_Fees")); return m_feeId; } const QString& MyMoneyStatementReader::Private::interestId(const MyMoneyAccount& invAcc) { scanCategories(m_interestId, invAcc, MyMoneyFile::instance()->income(), i18n("_Dividend")); return m_interestId; } QString MyMoneyStatementReader::Private::nameToId(const QString& name, MyMoneyAccount& parent) { // Adapted from KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) // Needed to find/create category:sub-categories MyMoneyFile* file = MyMoneyFile::instance(); QString id = file->categoryToAccount(name, Account::Type::Unknown); // if it does not exist, we have to create it if (id.isEmpty()) { MyMoneyAccount newAccount; MyMoneyAccount parentAccount = parent; newAccount.setName(name) ; int pos; // check for ':' in the name and use it as separator for a hierarchy while ((pos = newAccount.name().indexOf(MyMoneyFile::AccountSeperator)) != -1) { QString part = newAccount.name().left(pos); QString remainder = newAccount.name().mid(pos + 1); const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); if (existingAccount.id().isEmpty()) { newAccount.setName(part); newAccount.setAccountType(parentAccount.accountType()); file->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); }//end while newAccount.setAccountType(parentAccount.accountType()); // make sure we have a currency. If none is assigned, we assume base currency if (newAccount.currencyId().isEmpty()) newAccount.setCurrencyId(file->baseCurrency().id()); file->addAccount(newAccount, parentAccount); id = newAccount.id(); } return id; } QString MyMoneyStatementReader::Private::expenseId(const QString& name) { MyMoneyAccount parent = MyMoneyFile::instance()->expense(); return nameToId(name, parent); } QString MyMoneyStatementReader::Private::interestId(const QString& name) { MyMoneyAccount parent = MyMoneyFile::instance()->income(); return nameToId(name, parent); } QString MyMoneyStatementReader::Private::feeId(const QString& name) { MyMoneyAccount parent = MyMoneyFile::instance()->expense(); return nameToId(name, parent); } void MyMoneyStatementReader::Private::previouslyUsedCategories(const QString& investmentAccount, QString& feesId, QString& interestId) { feesId.clear(); interestId.clear(); MyMoneyFile* file = MyMoneyFile::instance(); try { MyMoneyAccount acc = file->account(investmentAccount); MyMoneyTransactionFilter filter(investmentAccount); filter.setReportAllSplits(false); // since we assume an investment account here, we need to collect the stock accounts as well filter.addAccount(acc.accountList()); QList< QPair > list; file->transactionList(list, filter); QList< QPair >::const_iterator it_t; for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { const MyMoneyTransaction& t = (*it_t).first; MyMoneySplit s = (*it_t).second; MyMoneyAccount acc = file->account(s.accountId()); // stock split shouldn't be fee or interest bacause it won't play nice with dissectTransaction // it was caused by processTransactionEntry adding splits in wrong order != with manual transaction entering if (acc.accountGroup() == Account::Type::Expense || acc.accountGroup() == Account::Type::Income) { foreach (auto sNew , t.splits()) { acc = file->account(sNew.accountId()); if (acc.accountGroup() != Account::Type::Expense && // shouldn't be fee acc.accountGroup() != Account::Type::Income && // shouldn't be interest (sNew.value() != sNew.shares() || // shouldn't be checking account... (sNew.value() == sNew.shares() && sNew.price() != MyMoneyMoney::ONE))) { // ...but sometimes it may look like checking account s = sNew; break; } } } MyMoneySplit assetAccountSplit; QList feeSplits; QList interestSplits; MyMoneySecurity security; MyMoneySecurity currency; eMyMoney::Split::InvestmentTransactionType transactionType; KMyMoneyUtils::dissectTransaction(t, s, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType); if (!feeSplits.isEmpty()) { feesId = feeSplits.first().accountId(); if (!interestId.isEmpty()) break; } if (!interestSplits.isEmpty()) { interestId = interestSplits.first().accountId(); if (!feesId.isEmpty()) break; } } } catch (const MyMoneyException &) { } } void MyMoneyStatementReader::Private::scanCategories(QString& id, const MyMoneyAccount& invAcc, const MyMoneyAccount& parentAccount, const QString& defaultName) { if (!scannedCategories) { previouslyUsedCategories(invAcc.id(), m_feeId, m_interestId); scannedCategories = true; } if (id.isEmpty()) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount acc = file->accountByName(defaultName); // if it does not exist, we have to create it if (acc.id().isEmpty()) { MyMoneyAccount parent = parentAccount; acc.setName(defaultName); acc.setAccountType(parent.accountType()); acc.setCurrencyId(parent.currencyId()); file->addAccount(acc, parent); } id = acc.id(); } } void MyMoneyStatementReader::Private::assignUniqueBankID(MyMoneySplit& s, const MyMoneyStatement::Transaction& t_in) { if (! t_in.m_strBankID.isEmpty()) { // make sure that id's are unique from this point on by appending a -# // postfix if needed QString base(t_in.m_strBankID); QString hash(base); int idx = 1; for (;;) { QMap::const_iterator it; it = uniqIds.constFind(hash); if (it == uniqIds.constEnd()) { uniqIds[hash] = true; break; } hash = QString("%1-%2").arg(base).arg(idx); ++idx; } s.setBankID(hash); } } void MyMoneyStatementReader::Private::setupPrice(MyMoneySplit &s, const MyMoneyAccount &splitAccount, const MyMoneyAccount &transactionAccount, const QDate &postDate) { if (transactionAccount.currencyId() != splitAccount.currencyId()) { // a currency converstion is needed assume that split has already a proper value MyMoneyFile* file = MyMoneyFile::instance(); MyMoneySecurity toCurrency = file->security(splitAccount.currencyId()); MyMoneySecurity fromCurrency = file->security(transactionAccount.currencyId()); // get the price for the transaction's date const MyMoneyPrice &price = file->price(fromCurrency.id(), toCurrency.id(), postDate); // if the price is valid calculate the shares if (price.isValid()) { const int fract = splitAccount.fraction(toCurrency); const MyMoneyMoney &shares = s.value() * price.rate(toCurrency.id()); s.setShares(shares.convert(fract)); qDebug("Setting second split shares to %s", qPrintable(s.shares().formatMoney(toCurrency.id(), 2))); } else { qDebug("No price entry was found to convert from '%s' to '%s' on '%s'", qPrintable(fromCurrency.tradingSymbol()), qPrintable(toCurrency.tradingSymbol()), qPrintable(postDate.toString(Qt::ISODate))); } } } MyMoneyStatementReader::MyMoneyStatementReader() : d(new Private), m_userAbort(false), m_autoCreatePayee(false), m_ft(0), m_progressCallback(0) { m_askPayeeCategory = KMyMoneyGlobalSettings::askForPayeeCategory(); } MyMoneyStatementReader::~MyMoneyStatementReader() { delete d; } bool MyMoneyStatementReader::anyTransactionAdded() const { return (d->transactionsAdded != 0) ? true : false; } void MyMoneyStatementReader::setAutoCreatePayee(bool create) { m_autoCreatePayee = create; } void MyMoneyStatementReader::setAskPayeeCategory(bool ask) { m_askPayeeCategory = ask; } bool MyMoneyStatementReader::import(const MyMoneyStatement& s, QStringList& messages) { // // For testing, save the statement to an XML file // (uncomment this line) // //MyMoneyStatement::writeXMLFile(s, "Imported.Xml"); // // Select the account // d->m_account = MyMoneyAccount(); d->m_brokerageAccount = MyMoneyAccount(); m_ft = new MyMoneyFileTransaction(); d->m_skipCategoryMatching = s.m_skipCategoryMatching; // if the statement source left some information about // the account, we use it to get the current data of it if (!s.m_accountId.isEmpty()) { try { d->m_account = MyMoneyFile::instance()->account(s.m_accountId); } catch (const MyMoneyException &) { qDebug("Received reference '%s' to unknown account in statement", qPrintable(s.m_accountId)); } } if (d->m_account.id().isEmpty()) { d->m_account.setName(s.m_strAccountName); d->m_account.setNumber(s.m_strAccountNumber); switch (s.m_eType) { case MyMoneyStatement::etCheckings: d->m_account.setAccountType(Account::Type::Checkings); break; case MyMoneyStatement::etSavings: d->m_account.setAccountType(Account::Type::Savings); break; case MyMoneyStatement::etInvestment: //testing support for investment statements! //m_userAbort = true; //KMessageBox::error(kmymoney, i18n("This is an investment statement. These are not supported currently."), i18n("Critical Error")); d->m_account.setAccountType(Account::Type::Investment); break; case MyMoneyStatement::etCreditCard: d->m_account.setAccountType(Account::Type::CreditCard); break; default: d->m_account.setAccountType(Account::Type::Unknown); break; } // we ask the user only if we have some transactions to process if (!m_userAbort && s.m_listTransactions.count() > 0) m_userAbort = ! selectOrCreateAccount(Select, d->m_account); } // see if we need to update some values stored with the account if (d->m_account.value("lastStatementBalance") != s.m_closingBalance.toString() || d->m_account.value("lastImportedTransactionDate") != s.m_dateEnd.toString(Qt::ISODate)) { if (s.m_closingBalance != MyMoneyMoney::autoCalc) { d->m_account.setValue("lastStatementBalance", s.m_closingBalance.toString()); if (s.m_dateEnd.isValid()) { d->m_account.setValue("lastImportedTransactionDate", s.m_dateEnd.toString(Qt::ISODate)); } } try { MyMoneyFile::instance()->modifyAccount(d->m_account); } catch (const MyMoneyException &) { qDebug("Updating account in MyMoneyStatementReader::startImport failed"); } } if (!d->m_account.name().isEmpty()) messages += i18n("Importing statement for account %1", d->m_account.name()); else if (s.m_listTransactions.count() == 0) messages += i18n("Importing statement without transactions"); qDebug("Importing statement for '%s'", qPrintable(d->m_account.name())); // // Process the securities // signalProgress(0, s.m_listSecurities.count(), "Importing Statement ..."); int progress = 0; QList::const_iterator it_s = s.m_listSecurities.begin(); while (it_s != s.m_listSecurities.end()) { processSecurityEntry(*it_s); signalProgress(++progress, 0); ++it_s; } signalProgress(-1, -1); // // Process the transactions // if (!m_userAbort) { try { qDebug("Processing transactions (%s)", qPrintable(d->m_account.name())); signalProgress(0, s.m_listTransactions.count(), "Importing Statement ..."); int progress = 0; QList::const_iterator it_t = s.m_listTransactions.begin(); while (it_t != s.m_listTransactions.end() && !m_userAbort) { processTransactionEntry(*it_t); signalProgress(++progress, 0); ++it_t; } qDebug("Processing transactions done (%s)", qPrintable(d->m_account.name())); } catch (const MyMoneyException &e) { if (e.what() == "USERABORT") m_userAbort = true; else qDebug("Caught exception from processTransactionEntry() not caused by USERABORT: %s", qPrintable(e.what())); } signalProgress(-1, -1); } // // process price entries // if (!m_userAbort) { try { signalProgress(0, s.m_listPrices.count(), "Importing Statement ..."); QList slist = MyMoneyFile::instance()->securityList(); QList::const_iterator it_s; for (it_s = slist.constBegin(); it_s != slist.constEnd(); ++it_s) { d->securitiesBySymbol[(*it_s).tradingSymbol()] = *it_s; d->securitiesByName[(*it_s).name()] = *it_s; } int progress = 0; QList::const_iterator it_p = s.m_listPrices.begin(); while (it_p != s.m_listPrices.end()) { processPriceEntry(*it_p); signalProgress(++progress, 0); ++it_p; } } catch (const MyMoneyException &e) { if (e.what() == "USERABORT") m_userAbort = true; else qDebug("Caught exception from processPriceEntry() not caused by USERABORT: %s", qPrintable(e.what())); } signalProgress(-1, -1); } bool rc = false; // delete all payees created in vain int payeeCount = d->payees.count(); QList::const_iterator it_p; for (it_p = d->payees.constBegin(); it_p != d->payees.constEnd(); ++it_p) { try { MyMoneyFile::instance()->removePayee(*it_p); --payeeCount; } catch (const MyMoneyException &) { // if we can't delete it, it must be in use which is ok for us } } if (s.m_closingBalance.isAutoCalc()) { messages += i18n(" Statement balance is not contained in statement."); } else { messages += i18n(" Statement balance on %1 is reported to be %2", s.m_dateEnd.toString(Qt::ISODate), s.m_closingBalance.formatMoney("", 2)); } messages += i18n(" Transactions"); messages += i18np(" %1 processed", " %1 processed", d->transactionsCount); messages += i18ncp("x transactions have been added", " %1 added", " %1 added", d->transactionsAdded); messages += i18np(" %1 matched", " %1 matched", d->transactionsMatched); messages += i18np(" %1 duplicate", " %1 duplicates", d->transactionsDuplicate); messages += i18n(" Payees"); messages += i18ncp("x transactions have been created", " %1 created", " %1 created", payeeCount); messages += QString(); // remove the Don't ask again entries KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group(QString::fromLatin1("Notification Messages")); QStringList::ConstIterator it; for (it = m_dontAskAgain.constBegin(); it != m_dontAskAgain.constEnd(); ++it) { grp.deleteEntry(*it); } config->sync(); m_dontAskAgain.clear(); rc = !m_userAbort; // finish the transaction if (rc) m_ft->commit(); delete m_ft; m_ft = 0; qDebug("Importing statement for '%s' done", qPrintable(d->m_account.name())); return rc; } void MyMoneyStatementReader::processPriceEntry(const MyMoneyStatement::Price& p_in) { MyMoneyFile* file = MyMoneyFile::instance(); QString currency = file->baseCurrency().id(); QString security; if (!p_in.m_strCurrency.isEmpty()) { security = p_in.m_strSecurity; currency = p_in.m_strCurrency; } else if (d->securitiesBySymbol.contains(p_in.m_strSecurity)) { security = d->securitiesBySymbol[p_in.m_strSecurity].id(); currency = file->security(file->security(security).tradingCurrency()).id(); } else if (d->securitiesByName.contains(p_in.m_strSecurity)) { security = d->securitiesByName[p_in.m_strSecurity].id(); currency = file->security(file->security(security).tradingCurrency()).id(); } else return; MyMoneyPrice price(security, currency, p_in.m_date, p_in.m_amount, p_in.m_sourceName.isEmpty() ? i18n("Prices Importer") : p_in.m_sourceName); MyMoneyFile::instance()->addPrice(price); } void MyMoneyStatementReader::processSecurityEntry(const MyMoneyStatement::Security& sec_in) { // For a security entry, we will just make sure the security exists in the // file. It will not get added to the investment account until it's called // for in a transaction. MyMoneyFile* file = MyMoneyFile::instance(); // check if we already have the security // In a statement, we do not know what type of security this is, so we will // not use type as a matching factor. MyMoneySecurity security; QList list = file->securityList(); QList::ConstIterator it = list.constBegin(); while (it != list.constEnd() && security.id().isEmpty()) { if (matchNotEmpty(sec_in.m_strSymbol, (*it).tradingSymbol()) || matchNotEmpty(sec_in.m_strName, (*it).name())) { security = *it; } ++it; } // if the security was not found, we have to create it while not forgetting // to setup the type if (security.id().isEmpty()) { security.setName(sec_in.m_strName); security.setTradingSymbol(sec_in.m_strSymbol); security.setTradingCurrency(file->baseCurrency().id()); security.setValue("kmm-security-id", sec_in.m_strId); security.setValue("kmm-online-source", "Stooq"); security.setSecurityType(Security::Type::Stock); MyMoneyFileTransaction ft; try { file->addSecurity(security); ft.commit(); qDebug() << "Created " << security.name() << " with id " << security.id(); } catch (const MyMoneyException &e) { KMessageBox::error(0, i18n("Error creating security record: %1", e.what()), i18n("Error")); } } else { qDebug() << "Found " << security.name() << " with id " << security.id(); } } void MyMoneyStatementReader::processTransactionEntry(const MyMoneyStatement::Transaction& statementTransactionUnderImport) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyTransaction transactionUnderImport; QString dbgMsg; dbgMsg = QString("Process on: '%1', id: '%3', amount: '%2', fees: '%4'") .arg(statementTransactionUnderImport.m_datePosted.toString(Qt::ISODate)) .arg(statementTransactionUnderImport.m_amount.formatMoney("", 2)) .arg(statementTransactionUnderImport.m_strBankID) .arg(statementTransactionUnderImport.m_fees.formatMoney("", 2)); qDebug("%s", qPrintable(dbgMsg)); // mark it imported for the view transactionUnderImport.setImported(); // TODO (Ace) We can get the commodity from the statement!! // Although then we would need UI to verify transactionUnderImport.setCommodity(d->m_account.currencyId()); transactionUnderImport.setPostDate(statementTransactionUnderImport.m_datePosted); transactionUnderImport.setMemo(statementTransactionUnderImport.m_strMemo); MyMoneySplit s1; MyMoneySplit s2; MyMoneySplit sFees; MyMoneySplit sBrokerage; s1.setMemo(statementTransactionUnderImport.m_strMemo); s1.setValue(statementTransactionUnderImport.m_amount + statementTransactionUnderImport.m_fees); s1.setShares(s1.value()); s1.setNumber(statementTransactionUnderImport.m_strNumber); // set these values if a transfer split is needed at the very end. MyMoneyMoney transfervalue; // If the user has chosen to import into an investment account, determine the correct account to use MyMoneyAccount thisaccount = d->m_account; QString brokerageactid; if (thisaccount.accountType() == Account::Type::Investment) { // determine the brokerage account brokerageactid = d->m_account.value("kmm-brokerage-account").toUtf8(); if (brokerageactid.isEmpty()) { brokerageactid = file->accountByName(statementTransactionUnderImport.m_strBrokerageAccount).id(); } if (brokerageactid.isEmpty()) { brokerageactid = file->nameToAccount(statementTransactionUnderImport.m_strBrokerageAccount); } if (brokerageactid.isEmpty()) { brokerageactid = file->nameToAccount(thisaccount.brokerageName()); } if (brokerageactid.isEmpty()) { brokerageactid = SelectBrokerageAccount(); } // find the security transacted, UNLESS this transaction didn't // involve any security. if ((statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaNone) // eaInterest transactions MAY have a security. // && (t_in.m_eAction != MyMoneyStatement::Transaction::eaInterest) && (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaFees)) { // the correct account is the stock account which matches two criteria: // (1) it is a sub-account of the selected investment account, and // (2a) the symbol of the underlying security matches the security of the // transaction, or // (2b) the name of the security matches the name of the security of the transaction. // search through each subordinate account bool found = false; QStringList accounts = thisaccount.accountList(); QStringList::const_iterator it_account = accounts.constBegin(); QString currencyid; while (!found && it_account != accounts.constEnd()) { currencyid = file->account(*it_account).currencyId(); MyMoneySecurity security = file->security(currencyid); if (matchNotEmpty(statementTransactionUnderImport.m_strSymbol, security.tradingSymbol()) || matchNotEmpty(statementTransactionUnderImport.m_strSecurity, security.name())) { thisaccount = file->account(*it_account); found = true; } ++it_account; } // If there was no stock account under the m_acccount investment account, // add one using the security. if (!found) { // The security should always be available, because the statement file // should separately list all the securities referred to in the file, // and when we found a security, we added it to the file. if (statementTransactionUnderImport.m_strSecurity.isEmpty()) { KMessageBox::information(0, i18n("This imported statement contains investment transactions with no security. These transactions will be ignored."), i18n("Security not found"), QString("BlankSecurity")); return; } else { MyMoneySecurity security; QList list = MyMoneyFile::instance()->securityList(); QList::ConstIterator it = list.constBegin(); while (it != list.constEnd() && security.id().isEmpty()) { if (matchNotEmpty(statementTransactionUnderImport.m_strSymbol, (*it).tradingSymbol()) || matchNotEmpty(statementTransactionUnderImport.m_strSecurity, (*it).name())) { security = *it; } ++it; } if (!security.id().isEmpty()) { thisaccount = MyMoneyAccount(); thisaccount.setName(security.name()); thisaccount.setAccountType(Account::Type::Stock); thisaccount.setCurrencyId(security.id()); currencyid = thisaccount.currencyId(); file->addAccount(thisaccount, d->m_account); qDebug() << Q_FUNC_INFO << ": created account " << thisaccount.id() << " for security " << statementTransactionUnderImport.m_strSecurity << " under account " << d->m_account.id(); } // this security does not exist in the file. else { // This should be rare. A statement should have a security entry for any // of the securities referred to in the transactions. The only way to get // here is if that's NOT the case. int ret = KMessageBox::warningContinueCancel(0, i18n("
This investment account does not contain the \"%1\" security.
" "
Transactions involving this security will be ignored.
", statementTransactionUnderImport.m_strSecurity), i18n("Security not found"), KStandardGuiItem::cont(), KStandardGuiItem::cancel()); if (ret == KMessageBox::Cancel) { m_userAbort = true; } return; } } } // Don't update price if there is no price information contained in the transaction if (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaCashDividend && statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaShrsin && statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaShrsout) { // update the price, while we're here. in the future, this should be // an option QString basecurrencyid = file->baseCurrency().id(); const MyMoneyPrice &price = file->price(currencyid, basecurrencyid, statementTransactionUnderImport.m_datePosted, true); if (!price.isValid() && ((!statementTransactionUnderImport.m_amount.isZero() && !statementTransactionUnderImport.m_shares.isZero()) || !statementTransactionUnderImport.m_price.isZero())) { MyMoneyPrice newprice; if (!statementTransactionUnderImport.m_price.isZero()) { newprice = MyMoneyPrice(currencyid, basecurrencyid, statementTransactionUnderImport.m_datePosted, statementTransactionUnderImport.m_price.abs(), i18n("Statement Importer")); } else { newprice = MyMoneyPrice(currencyid, basecurrencyid, statementTransactionUnderImport.m_datePosted, (statementTransactionUnderImport.m_amount / statementTransactionUnderImport.m_shares).abs(), i18n("Statement Importer")); } file->addPrice(newprice); } } } s1.setAccountId(thisaccount.id()); d->assignUniqueBankID(s1, statementTransactionUnderImport); if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaReinvestDividend) { s1.setAction(MyMoneySplit::ActionReinvestDividend); s1.setShares(statementTransactionUnderImport.m_shares); if (!statementTransactionUnderImport.m_price.isZero()) { s1.setPrice(statementTransactionUnderImport.m_price); } else { if (statementTransactionUnderImport.m_shares.isZero()) { KMessageBox::information(0, i18n("This imported statement contains investment transactions with no share amount. These transactions will be ignored."), i18n("No share amount provided"), QString("BlankAmount")); return; } MyMoneyMoney total = -statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees; - s1.setPrice((total / statementTransactionUnderImport.m_shares).convertPrecision(file->security(thisaccount.currencyId()).pricePrecision())); + s1.setPrice(MyMoneyMoney((total / statementTransactionUnderImport.m_shares).convertPrecision(file->security(thisaccount.currencyId()).pricePrecision()))); } s2.setMemo(statementTransactionUnderImport.m_strMemo); if (statementTransactionUnderImport.m_strInterestCategory.isEmpty()) s2.setAccountId(d->interestId(thisaccount)); else s2.setAccountId(d->interestId(statementTransactionUnderImport.m_strInterestCategory)); s2.setShares(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees); s2.setValue(s2.shares()); } else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaCashDividend) { // Cash dividends require setting 2 splits to get all of the information // in. Split #1 will be the income split, and we'll set it to the first // income account. This is a hack, but it's needed in order to get the // amount into the transaction. if (statementTransactionUnderImport.m_strInterestCategory.isEmpty()) s1.setAccountId(d->interestId(thisaccount)); else {// Ensure category sub-accounts are dealt with properly s1.setAccountId(d->interestId(statementTransactionUnderImport.m_strInterestCategory)); } s1.setShares(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees); s1.setValue(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees); // Split 2 will be the zero-amount investment split that serves to // mark this transaction as a cash dividend and note which stock account // it belongs to. s2.setMemo(statementTransactionUnderImport.m_strMemo); s2.setAction(MyMoneySplit::ActionDividend); s2.setAccountId(thisaccount.id()); /* at this point any fees have been taken into account already * so don't deduct them again. * BUG 322381 */ transfervalue = statementTransactionUnderImport.m_amount; } else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaInterest) { if (statementTransactionUnderImport.m_strInterestCategory.isEmpty()) s1.setAccountId(d->interestId(thisaccount)); else {// Ensure category sub-accounts are dealt with properly if (statementTransactionUnderImport.m_amount.isPositive()) s1.setAccountId(d->interestId(statementTransactionUnderImport.m_strInterestCategory)); else s1.setAccountId(d->expenseId(statementTransactionUnderImport.m_strInterestCategory)); } s1.setShares(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees); s1.setValue(-statementTransactionUnderImport.m_amount - statementTransactionUnderImport.m_fees); /// *********** Add split as per Div ********** // Split 2 will be the zero-amount investment split that serves to // mark this transaction as a cash dividend and note which stock account // it belongs to. s2.setMemo(statementTransactionUnderImport.m_strMemo); s2.setAction(MyMoneySplit::ActionInterestIncome); s2.setAccountId(thisaccount.id()); transfervalue = statementTransactionUnderImport.m_amount; } else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaFees) { if (statementTransactionUnderImport.m_strInterestCategory.isEmpty()) s1.setAccountId(d->feeId(thisaccount)); else// Ensure category sub-accounts are dealt with properly s1.setAccountId(d->feeId(statementTransactionUnderImport.m_strInterestCategory)); s1.setShares(statementTransactionUnderImport.m_amount); s1.setValue(statementTransactionUnderImport.m_amount); transfervalue = statementTransactionUnderImport.m_amount; } else if ((statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaBuy) || (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaSell)) { s1.setAction(MyMoneySplit::ActionBuyShares); if (!statementTransactionUnderImport.m_price.isZero()) { s1.setPrice(statementTransactionUnderImport.m_price.abs()); } else if (!statementTransactionUnderImport.m_shares.isZero()) { MyMoneyMoney total = statementTransactionUnderImport.m_amount + statementTransactionUnderImport.m_fees.abs(); - s1.setPrice((total / statementTransactionUnderImport.m_shares).abs().convertPrecision(file->security(thisaccount.currencyId()).pricePrecision())); + s1.setPrice(MyMoneyMoney((total / statementTransactionUnderImport.m_shares).abs().convertPrecision(file->security(thisaccount.currencyId()).pricePrecision()))); } if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaBuy) s1.setShares(statementTransactionUnderImport.m_shares.abs()); else s1.setShares(-statementTransactionUnderImport.m_shares.abs()); s1.setValue(-(statementTransactionUnderImport.m_amount + statementTransactionUnderImport.m_fees.abs())); transfervalue = statementTransactionUnderImport.m_amount; } else if ((statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaShrsin) || (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaShrsout)) { s1.setValue(MyMoneyMoney()); s1.setShares(statementTransactionUnderImport.m_shares); s1.setAction(MyMoneySplit::ActionAddShares); } else if (statementTransactionUnderImport.m_eAction == MyMoneyStatement::Transaction::eaNone) { // User is attempting to import a non-investment transaction into this // investment account. This is not supportable the way KMyMoney is // written. However, if a user has an associated brokerage account, // we can stuff the transaction there. QString brokerageactid = d->m_account.value("kmm-brokerage-account").toUtf8(); if (brokerageactid.isEmpty()) { brokerageactid = file->accountByName(d->m_account.brokerageName()).id(); } if (! brokerageactid.isEmpty()) { s1.setAccountId(brokerageactid); d->assignUniqueBankID(s1, statementTransactionUnderImport); // Needed to satisfy the bankid check below. thisaccount = file->account(brokerageactid); } else { // Warning!! Your transaction is being thrown away. } } if (!statementTransactionUnderImport.m_fees.isZero()) { sFees.setMemo(i18n("(Fees) %1", statementTransactionUnderImport.m_strMemo)); sFees.setValue(statementTransactionUnderImport.m_fees); sFees.setShares(statementTransactionUnderImport.m_fees); sFees.setAccountId(d->feeId(thisaccount)); } } else { // For non-investment accounts, just use the selected account // Note that it is perfectly reasonable to import an investment statement into a non-investment account // if you really want. The investment-specific information, such as number of shares and action will // be discarded in that case. s1.setAccountId(d->m_account.id()); d->assignUniqueBankID(s1, statementTransactionUnderImport); } QString payeename = statementTransactionUnderImport.m_strPayee; if (!payeename.isEmpty()) { qDebug() << QLatin1String("Start matching payee") << payeename; QString payeeid; try { QList pList = file->payeeList(); QList::const_iterator it_p; QMap matchMap; for (it_p = pList.constBegin(); it_p != pList.constEnd(); ++it_p) { bool ignoreCase; QStringList keys; QStringList::const_iterator it_s; const MyMoneyPayee::payeeMatchType matchType = (*it_p).matchData(ignoreCase, keys); switch (matchType) { case MyMoneyPayee::matchDisabled: break; case MyMoneyPayee::matchName: case MyMoneyPayee::matchNameExact: keys << QString("%1").arg(QRegExp::escape((*it_p).name())); if(matchType == MyMoneyPayee::matchNameExact) { keys.clear(); keys << QString("^%1$").arg(QRegExp::escape((*it_p).name())); } // intentional fall through case MyMoneyPayee::matchKey: for (it_s = keys.constBegin(); it_s != keys.constEnd(); ++it_s) { QRegExp exp(*it_s, ignoreCase ? Qt::CaseInsensitive : Qt::CaseSensitive); if (exp.indexIn(payeename) != -1) { qDebug("Found match with '%s' on '%s'", qPrintable(payeename), qPrintable((*it_p).name())); matchMap[exp.matchedLength()] = (*it_p).id(); } } break; } } // at this point we can have several scenarios: // a) multiple matches // b) a single match // c) no match at all // // for c) we just do nothing, for b) we take the one we found // in case of a) we take the one with the largest matchedLength() // which happens to be the last one in the map if (matchMap.count() > 1) { qDebug("Multiple matches"); QMap::const_iterator it_m = matchMap.constEnd(); --it_m; payeeid = *it_m; } else if (matchMap.count() == 1) { qDebug("Single matches"); payeeid = *(matchMap.constBegin()); } // if we did not find a matching payee, we throw an exception and try to create it if (payeeid.isEmpty()) throw MYMONEYEXCEPTION("payee not matched"); s1.setPayeeId(payeeid); } catch (const MyMoneyException &) { MyMoneyPayee payee; int rc = KMessageBox::Yes; if (m_autoCreatePayee == false) { // Ask the user if that is what he intended to do? QString msg = i18n("Do you want to add \"%1\" as payee/receiver?\n\n", payeename); msg += i18n("Selecting \"Yes\" will create the payee, \"No\" will skip " "creation of a payee record and remove the payee information " "from this transaction. Selecting \"Cancel\" aborts the import " "operation.\n\nIf you select \"No\" here and mark the \"Do not ask " "again\" checkbox, the payee information for all following transactions " "referencing \"%1\" will be removed.", payeename); QString askKey = QString("Statement-Import-Payee-") + payeename; if (!m_dontAskAgain.contains(askKey)) { m_dontAskAgain += askKey; } rc = KMessageBox::questionYesNoCancel(0, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), KStandardGuiItem::cancel(), askKey); } if (rc == KMessageBox::Yes) { // for now, we just add the payee to the pool and turn // on simple name matching, so that future transactions // with the same name don't get here again. // // In the future, we could open a dialog and ask for // all the other attributes of the payee, but since this // is called in the context of an automatic procedure it // might distract the user. payee.setName(payeename); payee.setMatchData(MyMoneyPayee::matchKey, true, QStringList() << QString("^%1$").arg(QRegExp::escape(payeename))); if (m_askPayeeCategory) { // We use a QPointer because the dialog may get deleted // during exec() if the parent of the dialog gets deleted. // In that case the guarded ptr will reset to 0. QPointer dialog = new QDialog(kmymoney); dialog->setWindowTitle(i18n("Default Category for Payee")); dialog->setModal(true); QWidget *mainWidget = new QWidget; QVBoxLayout *topcontents = new QVBoxLayout(mainWidget); //add in caption? and account combo here QLabel *label1 = new QLabel(i18n("Please select a default category for payee '%1'", payeename)); topcontents->addWidget(label1); auto filterProxyModel = new AccountNamesFilterProxyModel(this); filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode()); filterProxyModel->addAccountGroup(QVector {Account::Type::Asset, Account::Type::Liability, Account::Type::Equity, Account::Type::Income, Account::Type::Expense}); auto const model = Models::instance()->accountsModel(); filterProxyModel->setSourceModel(model); filterProxyModel->setSourceColumns(model->getColumns()); filterProxyModel->sort((int)eAccountsModel::Column::Account); QPointer accountCombo = new KMyMoneyAccountCombo(filterProxyModel); topcontents->addWidget(accountCombo); mainWidget->setLayout(topcontents); QVBoxLayout *mainLayout = new QVBoxLayout; QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::No|QDialogButtonBox::Yes); dialog->setLayout(mainLayout); mainLayout->addWidget(mainWidget); dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); mainLayout->addWidget(buttonBox); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Yes), KGuiItem(i18n("Save Category"))); KGuiItem::assign(buttonBox->button(QDialogButtonBox::No), KGuiItem(i18n("No Category"))); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KGuiItem(i18n("Abort"))); int result = dialog->exec(); QString accountId; if (accountCombo && !accountCombo->getSelected().isEmpty()) { accountId = accountCombo->getSelected(); } delete dialog; //if they hit yes instead of no, then grab setting of account combo if (result == QDialog::Accepted) { payee.setDefaultAccountId(accountId); } else if (result != QDialog::Rejected) { //add cancel button? and throw exception like below throw MYMONEYEXCEPTION("USERABORT"); } } try { file->addPayee(payee); qDebug("Payee '%s' created", qPrintable(payee.name())); d->payees << payee; payeeid = payee.id(); s1.setPayeeId(payeeid); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to add payee/receiver"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else if (rc == KMessageBox::No) { s1.setPayeeId(QString()); } else { throw MYMONEYEXCEPTION("USERABORT"); } } if (thisaccount.accountType() != Account::Type::Stock) { // // Fill in other side of the transaction (category/etc) based on payee // // Note, this logic is lifted from KLedgerView::slotPayeeChanged(), // however this case is more complicated, because we have an amount and // a memo. We just don't have the other side of the transaction. // // We'll search for the most recent transaction in this account with // this payee. If this reference transaction is a simple 2-split // transaction, it's simple. If it's a complex split, and the amounts // are different, we have a problem. Somehow we have to balance the // transaction. For now, we'll leave it unbalanced, and let the user // handle it. // const MyMoneyPayee& payeeObj = MyMoneyFile::instance()->payee(payeeid); if (statementTransactionUnderImport.m_listSplits.isEmpty() && payeeObj.defaultAccountEnabled()) { MyMoneyAccount splitAccount = file->account(payeeObj.defaultAccountId()); MyMoneySplit s; s.setReconcileFlag(eMyMoney::Split::State::Cleared); s.clearId(); s.setBankID(QString()); s.setShares(-s1.shares()); s.setValue(-s1.value()); s.setAccountId(payeeObj.defaultAccountId()); s.setMemo(transactionUnderImport.memo()); s.setPayeeId(payeeid); d->setupPrice(s, splitAccount, d->m_account, statementTransactionUnderImport.m_datePosted); transactionUnderImport.addSplit(s); file->addVATSplit(transactionUnderImport, d->m_account, splitAccount, statementTransactionUnderImport.m_amount); } else if (statementTransactionUnderImport.m_listSplits.isEmpty() && !d->m_skipCategoryMatching) { MyMoneyTransactionFilter filter(thisaccount.id()); filter.addPayee(payeeid); QList list = file->transactionList(filter); if (!list.empty()) { // Default to using the most recent transaction as the reference MyMoneyTransaction t_old = list.last(); // if there is more than one matching transaction, try to be a little // smart about which one we take. for now, we'll see if there's one // with the same VALUE as our imported transaction, and if so take that one. if (list.count() > 1) { QList::ConstIterator it_trans = list.constEnd(); if (it_trans != list.constBegin()) --it_trans; while (it_trans != list.constBegin()) { MyMoneySplit s = (*it_trans).splitByAccount(thisaccount.id()); if (s.value() == s1.value()) { // keep searching if this transaction references a closed account if (!MyMoneyFile::instance()->referencesClosedAccount(*it_trans)) { t_old = *it_trans; break; } } --it_trans; } // check constBegin, just in case if (it_trans == list.constBegin()) { MyMoneySplit s = (*it_trans).splitByAccount(thisaccount.id()); if (s.value() == s1.value()) { t_old = *it_trans; } } } // Only copy the splits if the transaction found does not reference a closed account if (!MyMoneyFile::instance()->referencesClosedAccount(t_old)) { QList::ConstIterator it_split; for (it_split = t_old.splits().constBegin(); it_split != t_old.splits().constEnd(); ++it_split) { // We don't need the split that covers this account, // we just need the other ones. if ((*it_split).accountId() != thisaccount.id()) { MyMoneySplit s(*it_split); s.setReconcileFlag(eMyMoney::Split::State::NotReconciled); s.clearId(); s.setBankID(QString()); s.removeMatch(); if (t_old.splits().count() == 2) { s.setShares(-s1.shares()); s.setValue(-s1.value()); s.setMemo(s1.memo()); } MyMoneyAccount splitAccount = file->account(s.accountId()); qDebug("Adding second split to %s(%s)", qPrintable(splitAccount.name()), qPrintable(s.accountId())); d->setupPrice(s, splitAccount, d->m_account, statementTransactionUnderImport.m_datePosted); transactionUnderImport.addSplit(s); } } } } } } } s1.setReconcileFlag(statementTransactionUnderImport.m_reconcile); // Add the 'account' split if it's needed if (! transfervalue.isZero()) { // in case the transaction has a reference to the brokerage account, we use it // but if brokerageactid has already been set, keep that. if (!statementTransactionUnderImport.m_strBrokerageAccount.isEmpty() && brokerageactid.isEmpty()) { brokerageactid = file->nameToAccount(statementTransactionUnderImport.m_strBrokerageAccount); } if (brokerageactid.isEmpty()) { brokerageactid = file->accountByName(statementTransactionUnderImport.m_strBrokerageAccount).id(); } // There is no BrokerageAccount so have to nowhere to put this split. if (!brokerageactid.isEmpty()) { sBrokerage.setMemo(statementTransactionUnderImport.m_strMemo); sBrokerage.setValue(transfervalue); sBrokerage.setShares(transfervalue); sBrokerage.setAccountId(brokerageactid); sBrokerage.setReconcileFlag(statementTransactionUnderImport.m_reconcile); MyMoneyAccount splitAccount = file->account(sBrokerage.accountId()); d->setupPrice(sBrokerage, splitAccount, d->m_account, statementTransactionUnderImport.m_datePosted); } } if (!(sBrokerage == MyMoneySplit())) transactionUnderImport.addSplit(sBrokerage); if (!(sFees == MyMoneySplit())) transactionUnderImport.addSplit(sFees); if (!(s2 == MyMoneySplit())) transactionUnderImport.addSplit(s2); transactionUnderImport.addSplit(s1); if ((statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaReinvestDividend) && (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaCashDividend) && (statementTransactionUnderImport.m_eAction != MyMoneyStatement::Transaction::eaInterest) ) { //****************************************** // process splits //****************************************** QList::const_iterator it_s; for (it_s = statementTransactionUnderImport.m_listSplits.begin(); it_s != statementTransactionUnderImport.m_listSplits.end(); ++it_s) { MyMoneySplit s3; s3.setAccountId((*it_s).m_accountId); MyMoneyAccount acc = file->account(s3.accountId()); s3.setPayeeId(s1.payeeId()); s3.setMemo((*it_s).m_strMemo); s3.setShares((*it_s).m_amount); s3.setValue((*it_s).m_amount); s3.setReconcileFlag((*it_s).m_reconcile); d->setupPrice(s3, acc, d->m_account, statementTransactionUnderImport.m_datePosted); transactionUnderImport.addSplit(s3); } } // Add the transaction try { // check for matches already stored in the engine TransactionMatchFinder::MatchResult result; TransactionMatcher matcher(thisaccount); d->transactionsCount++; ExistingTransactionMatchFinder existingTrMatchFinder(KMyMoneyGlobalSettings::matchInterval()); result = existingTrMatchFinder.findMatch(transactionUnderImport, s1); if (result != TransactionMatchFinder::MatchNotFound) { MyMoneyTransaction matchedTransaction = existingTrMatchFinder.getMatchedTransaction(); if (!matchedTransaction.isImported() || result == TransactionMatchFinder::MatchPrecise) { // don't match with just imported transaction MyMoneySplit matchedSplit = existingTrMatchFinder.getMatchedSplit(); handleMatchingOfExistingTransaction(matcher, matchedTransaction, matchedSplit, transactionUnderImport, s1, result); return; } } addTransaction(transactionUnderImport); ScheduledTransactionMatchFinder scheduledTrMatchFinder(thisaccount, KMyMoneyGlobalSettings::matchInterval()); result = scheduledTrMatchFinder.findMatch(transactionUnderImport, s1); if (result != TransactionMatchFinder::MatchNotFound) { MyMoneySplit matchedSplit = scheduledTrMatchFinder.getMatchedSplit(); MyMoneySchedule matchedSchedule = scheduledTrMatchFinder.getMatchedSchedule(); handleMatchingOfScheduledTransaction(matcher, matchedSchedule, matchedSplit, transactionUnderImport, s1); return; } } catch (const MyMoneyException &e) { QString message(i18n("Problem adding or matching imported transaction with id '%1': %2", statementTransactionUnderImport.m_strBankID, e.what())); qDebug("%s", qPrintable(message)); int result = KMessageBox::warningContinueCancel(0, message); if (result == KMessageBox::Cancel) throw MYMONEYEXCEPTION("USERABORT"); } } QString MyMoneyStatementReader::SelectBrokerageAccount() { if (d->m_brokerageAccount.id().isEmpty()) { d->m_brokerageAccount.setAccountType(Account::Type::Checkings); if (!m_userAbort) m_userAbort = ! selectOrCreateAccount(Select, d->m_brokerageAccount); } return d->m_brokerageAccount.id(); } bool MyMoneyStatementReader::selectOrCreateAccount(const SelectCreateMode /*mode*/, MyMoneyAccount& account) { bool result = false; MyMoneyFile* file = MyMoneyFile::instance(); QString accountId; // Try to find an existing account in the engine which matches this one. // There are two ways to be a "matching account". The account number can // match the statement account OR the "StatementKey" property can match. // Either way, we'll update the "StatementKey" property for next time. QString accountNumber = account.number(); if (! accountNumber.isEmpty()) { // Get a list of all accounts QList accounts; file->accountList(accounts); // Iterate through them QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { if ( ((*it_account).value("StatementKey") == accountNumber) || ((*it_account).number() == accountNumber) ) { MyMoneyAccount newAccount((*it_account).id(), account); account = newAccount; accountId = (*it_account).id(); break; } ++it_account; } } QString msg = i18n("You have downloaded a statement for the following account:

"); msg += i18n(" - Account Name: %1", account.name()) + "
"; msg += i18n(" - Account Type: %1", MyMoneyAccount::accountTypeToString(account.accountType())) + "
"; msg += i18n(" - Account Number: %1", account.number()) + "
"; msg += "
"; if (!account.name().isEmpty()) { if (!accountId.isEmpty()) msg += i18n("Do you want to import transactions to this account?"); else msg += i18n("KMyMoney cannot determine which of your accounts to use. You can " "create a new account by pressing the Create button " "or select another one manually from the selection box below."); } else { msg += i18n("No account information has been found in the selected statement file. " "Please select an account using the selection box in the dialog or " "create a new account by pressing the Create button."); } eDialogs::Category type; if (account.accountType() == Account::Type::Checkings) { type = eDialogs::Category::checking; } else if (account.accountType() == Account::Type::Savings) { type = eDialogs::Category::savings; } else if (account.accountType() == Account::Type::Investment) { type = eDialogs::Category::investment; } else if (account.accountType() == Account::Type::CreditCard) { type = eDialogs::Category::creditCard; } else { type = static_cast(eDialogs::Category::asset | eDialogs::Category::liability); } QPointer accountSelect = new KAccountSelectDlg(type, "StatementImport", kmymoney); accountSelect->setHeader(i18n("Import transactions")); accountSelect->setDescription(msg); accountSelect->setAccount(account, accountId); accountSelect->setMode(false); accountSelect->showAbortButton(true); accountSelect->hideQifEntry(); QString accname; bool done = false; while (!done) { if (accountSelect->exec() == QDialog::Accepted && !accountSelect->selectedAccount().isEmpty()) { result = true; done = true; accountId = accountSelect->selectedAccount(); account = file->account(accountId); if (! accountNumber.isEmpty() && account.value("StatementKey") != accountNumber) { account.setValue("StatementKey", accountNumber); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(account); ft.commit(); accname = account.name(); } catch (const MyMoneyException &) { qDebug("Updating account in MyMoneyStatementReader::selectOrCreateAccount failed"); } } } else { if (accountSelect->aborted()) //throw MYMONEYEXCEPTION("USERABORT"); done = true; else KMessageBox::error(0, QLatin1String("") + i18n("You must select an account, create a new one, or press the Abort button.") + QLatin1String("")); } } delete accountSelect; return result; } const MyMoneyAccount& MyMoneyStatementReader::account() const { return d->m_account; } void MyMoneyStatementReader::setProgressCallback(void(*callback)(int, int, const QString&)) { m_progressCallback = callback; } void MyMoneyStatementReader::signalProgress(int current, int total, const QString& msg) { if (m_progressCallback != 0) (*m_progressCallback)(current, total, msg); } void MyMoneyStatementReader::handleMatchingOfExistingTransaction(TransactionMatcher & matcher, MyMoneyTransaction matchedTransaction, MyMoneySplit matchedSplit, MyMoneyTransaction & importedTransaction, const MyMoneySplit & importedSplit, const TransactionMatchFinder::MatchResult & matchResult) { switch (matchResult) { case TransactionMatchFinder::MatchNotFound: break; case TransactionMatchFinder::MatchDuplicate: d->transactionsDuplicate++; qDebug("Detected transaction duplicate"); break; case TransactionMatchFinder::MatchImprecise: case TransactionMatchFinder::MatchPrecise: addTransaction(importedTransaction); qDebug("Detected as match to transaction '%s'", qPrintable(matchedTransaction.id())); matcher.match(matchedTransaction, matchedSplit, importedTransaction, importedSplit, true); d->transactionsMatched++; break; } } void MyMoneyStatementReader::handleMatchingOfScheduledTransaction(TransactionMatcher & matcher, MyMoneySchedule matchedSchedule, MyMoneySplit matchedSplit, const MyMoneyTransaction & importedTransaction, const MyMoneySplit & importedSplit) { QPointer editor; if (askUserToEnterScheduleForMatching(matchedSchedule, importedSplit, importedTransaction)) { KEnterScheduleDlg dlg(0, matchedSchedule); editor = dlg.startEdit(); if (editor) { MyMoneyTransaction torig; try { // in case the amounts of the scheduled transaction and the // imported transaction differ, we need to update the amount // using the transaction editor. if (matchedSplit.shares() != importedSplit.shares() && !matchedSchedule.isFixed()) { // for now this only works with regular transactions and not // for investment transactions. As of this, we don't have // scheduled investment transactions anyway. auto se = dynamic_cast(editor.data()); if (se) { // the following call will update the amount field in the // editor and also adjust a possible VAT assignment. Make // sure to use only the absolute value of the amount, because // the editor keeps the sign in a different position (deposit, // withdrawal tab) KMyMoneyEdit* amount = dynamic_cast(se->haveWidget("amount")); if (amount) { amount->setValue(importedSplit.shares().abs()); se->slotUpdateAmount(importedSplit.shares().abs().toString()); // we also need to update the matchedSplit variable to // have the modified share/value. matchedSplit.setShares(importedSplit.shares()); matchedSplit.setValue(importedSplit.value()); } } } editor->createTransaction(torig, dlg.transaction(), dlg.transaction().splits().isEmpty() ? MyMoneySplit() : dlg.transaction().splits().front(), true); QString newId; if (editor->enterTransactions(newId, false, true)) { if (!newId.isEmpty()) { torig = MyMoneyFile::instance()->transaction(newId); matchedSchedule.setLastPayment(torig.postDate()); } matchedSchedule.setNextDueDate(matchedSchedule.nextPayment(matchedSchedule.nextDueDate())); MyMoneyFile::instance()->modifySchedule(matchedSchedule); } // now match the two transactions matcher.match(torig, matchedSplit, importedTransaction, importedSplit); d->transactionsMatched++; } catch (const MyMoneyException &e) { // make sure we get rid of the editor before // the KEnterScheduleDlg is destroyed delete editor; throw e; // rethrow } } // delete the editor delete editor; } } void MyMoneyStatementReader::addTransaction(MyMoneyTransaction& transaction) { MyMoneyFile* file = MyMoneyFile::instance(); file->addTransaction(transaction); d->transactionsAdded++; } bool MyMoneyStatementReader::askUserToEnterScheduleForMatching(const MyMoneySchedule& matchedSchedule, const MyMoneySplit& importedSplit, const MyMoneyTransaction & importedTransaction) const { QString scheduleName = matchedSchedule.name(); int currencyDenom = d->m_account.fraction(MyMoneyFile::instance()->currency(d->m_account.currencyId())); QString splitValue = importedSplit.value().formatMoney(currencyDenom); QString payeeName = MyMoneyFile::instance()->payee(importedSplit.payeeId()).name(); QString questionMsg = i18n("KMyMoney has found a scheduled transaction which matches an imported transaction.
" "Schedule name: %1
" "Transaction: %2 %3
" "Do you want KMyMoney to enter this schedule now so that the transaction can be matched?", scheduleName, splitValue, payeeName); // check that dates are within user's setting const int gap = std::abs(matchedSchedule.transaction().postDate().toJulianDay() - importedTransaction.postDate().toJulianDay()); if (gap > KMyMoneyGlobalSettings::matchInterval()) questionMsg = i18np("KMyMoney has found a scheduled transaction which matches an imported transaction.
" "Schedule name: %2
" "Transaction: %3 %4
" "The transaction dates are one day apart.
" "Do you want KMyMoney to enter this schedule now so that the transaction can be matched?", "KMyMoney has found a scheduled transaction which matches an imported transaction.
" "Schedule name: %2
" "Transaction: %3 %4
" "The transaction dates are %1 days apart.
" "Do you want KMyMoney to enter this schedule now so that the transaction can be matched?", gap ,scheduleName, splitValue, payeeName); const int userAnswer = KMessageBox::questionYesNo(0, QLatin1String("") + questionMsg + QLatin1String(""), i18n("Schedule found")); return (userAnswer == KMessageBox::Yes); } diff --git a/kmymoney/converter/transactionmatchfinder.h b/kmymoney/converter/transactionmatchfinder.h index 3cd7f6e33..6c2443ef0 100644 --- a/kmymoney/converter/transactionmatchfinder.h +++ b/kmymoney/converter/transactionmatchfinder.h @@ -1,168 +1,167 @@ /*************************************************************************** KMyMoney transaction importing module - base class for searching for a matching transaction copyright : (C) 2012 by Lukasz Maszczynski ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ - #ifndef TRANSACTIONMATCHFINDER_H #define TRANSACTIONMATCHFINDER_H #include #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyschedule.h" /** The class provides an interface for finding a MyMoneyTransaction match with the one being imported */ class TransactionMatchFinder { public: /// enumerates possible match results typedef enum { MatchNotFound, ///< no matching transaction found MatchImprecise, ///< matching transaction found MatchPrecise, ///< matching transaction found with exactly the same parameters MatchDuplicate ///< found transaction duplicate } MatchResult; /** Initializes the match finder. * @param matchWindow max number of days the transactions may vary and still be considered to be matching */ - TransactionMatchFinder(int matchWindow); + explicit TransactionMatchFinder(int matchWindow); virtual ~TransactionMatchFinder(); /** Searches for a matching transaction. See derived classes to learn where the transaction is looked for. * * @param transactionToMatch the imported transaction we want to match * @param splitToMatch the split of that transaction referencing the account we import into * @return the search result. There are several possible results: * - @ref MatchNotFound means that the imported transaction does not match any other transaction * - @ref MatchDuplicate means that the imported transaction is a duplicate of another transaction, * - @ref MatchImprecise means that the imported transaction matches another transaction, but the match * is not precise (e.g. transaction dates are not equal, but within matchWindow range) * - @ref MatchPrecise means that the imported transaction matches another transaction precisely */ TransactionMatchFinder::MatchResult findMatch(const MyMoneyTransaction& transactionToMatch, const MyMoneySplit& splitToMatch); /** Returns the matched split. * * @throws MyMoneyException if no match is found */ MyMoneySplit getMatchedSplit() const; /** Returns the matched transaction * * @throws MyMoneyException if no transaction was matched */ MyMoneyTransaction getMatchedTransaction() const; /** Returns the matched schedule * * @throws MyMoneyException if no schedule was matched */ MyMoneySchedule getMatchedSchedule() const; protected: int matchWindow; MyMoneyTransaction importedTransaction; //!< the imported transaction that is being matched MyMoneySplit importedSplit; //!< the imported transaction's split that is being matched MatchResult matchResult; //!< match result QScopedPointer matchedTransaction; //!< the transaction that matches the imported one QScopedPointer matchedSchedule; //!< the schedule that matches the imported transaction QScopedPointer matchedSplit; //!< the split that matches the imported one /** Prepares a list of match candidates for further processing, must be implemented in subclass */ virtual void createListOfMatchCandidates() = 0; /** Searches the list of match candidates for a real match, must be implemented in subclass */ virtual void findMatchInMatchCandidatesList() = 0; /** Checks whether one split is a duplicate of the other * @param split1 the first split * @param split2 the second split * @param amountVariation the max number of percent the amounts may differ and still be considered matching * @return true, if split2 is a duplicate of split1 (and vice-versa); false otherwise * * Splits are considered duplicates if both have the same (non-empty) bankId assigned and same amounts. */ bool splitsAreDuplicates(const MyMoneySplit & split1, const MyMoneySplit & split2, int amountVariation = 0) const; /** Checks whether one split matches the other * @param importedSplit the split being imported * @param existingSplit the existing split * @param amountVariation the max number of percent the amounts may differ and still be considered matching * @return true, if importedSplit matches existingSplit (not necessarily the other way around); false otherwise * * Splits are considered a match if both of them: * - reference the same account * - have matching bankID-s * - have matching ammounts * - have empty or matching payees * - are not marked as matched already */ bool splitsMatch(const MyMoneySplit & importedSplit, const MyMoneySplit & existingSplit, int amountVariation = 0) const; /** Checks whether splits reference the same account * @param split1 the first split * @param split2 the second split * @return true, if the same account is referenced by the splits; false otherwise */ bool splitsAccountsMatch(const MyMoneySplit & split1, const MyMoneySplit & split2) const; /** Checks whether splits amounts match * @param split1 the first split * @param split2 the second split * @param amountVariation the max number of percent the amounts may differ and still be considered matching * @return true, if amounts match; false otherwise */ bool splitsAmountsMatch(const MyMoneySplit & split1, const MyMoneySplit & split2, int amountVariation = 0) const; /** Checks whether the splits' bankId-s match * @param importedSplit the imported split * @param existingSplit the existing split * @return true, if bank ids match; false otherwise * * BankID-s match if any of the two occurs: * - they are equal * - bankId of existing split is empty */ bool splitsBankIdsMatch(const MyMoneySplit & importedSplit, const MyMoneySplit & existingSplit) const; /** Checks whether the splits' bankId-s are duplicated * @param split1 the first split * @param split2 the second split * @return true, if bank ids are equal and non-empty; false otherwise */ bool splitsBankIdsDuplicated(const MyMoneySplit & split1, const MyMoneySplit & split2) const; /** Checks whether payees of both splits match each other or at least one of them is empty * @param split1 the first split * @param split2 the second split * @return true, if splits reference the same payee or at least one payee is empty; false otherwise */ bool splitsPayeesMatchOrEmpty(const MyMoneySplit & split1, const MyMoneySplit & split2) const; /** Searches for a split in the transaction which matches imported transaction's split * @param transaction the transaction to look for the split in * @param amountVariation the max number of percent the split amounts may differ and still be considered matching */ void findMatchingSplit(const MyMoneyTransaction & transaction, int amountVariation); }; #endif // TRANSACTIONMATCHFINDER_H diff --git a/kmymoney/dialogs/editpersonaldatadlg.cpp b/kmymoney/dialogs/editpersonaldatadlg.cpp index 2affaa08b..8337da4d0 100644 --- a/kmymoney/dialogs/editpersonaldatadlg.cpp +++ b/kmymoney/dialogs/editpersonaldatadlg.cpp @@ -1,205 +1,205 @@ /*************************************************************************** knewfiledlg.cpp ------------------- copyright : (C) 2000 by Michael Edwardes email : mte@users.sourceforge.net (C) 2017 by Ł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. * * * ***************************************************************************/ #include "editpersonaldatadlg.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Headers #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneycontact.h" #include "ui_editpersonaldatadlg.h" class EditPersonalDataDlgPrivate { Q_DISABLE_COPY(EditPersonalDataDlgPrivate) Q_DECLARE_PUBLIC(EditPersonalDataDlg) public: - EditPersonalDataDlgPrivate(EditPersonalDataDlg *qq) : + explicit EditPersonalDataDlgPrivate(EditPersonalDataDlg *qq) : q_ptr(qq), ui(new Ui::EditPersonalDataDlg) { } ~EditPersonalDataDlgPrivate() { delete m_contact; delete ui; } void init(const QString& title) { Q_Q(EditPersonalDataDlg); m_contact = new MyMoneyContact(q); ui->setupUi(q); q->setModal(true); if (!title.isEmpty()) q->setWindowTitle(title); ui->kabcBtn->setEnabled(m_contact->ownerExists()); ui->userNameEdit->setFocus(); q->connect(ui->buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); q->connect(ui->buttonBox, &QDialogButtonBox::accepted, q, &EditPersonalDataDlg::okClicked); q->connect(ui->kabcBtn, &QAbstractButton::clicked, q, &EditPersonalDataDlg::loadFromAddressBook); } EditPersonalDataDlg *q_ptr; Ui::EditPersonalDataDlg *ui; MyMoneyContact *m_contact; QString userNameText; QString userStreetText; QString userTownText; QString userCountyText; QString userPostcodeText; QString userTelephoneText; QString userEmailText; }; EditPersonalDataDlg::EditPersonalDataDlg(QWidget *parent, const QString& title) : QDialog(parent), d_ptr(new EditPersonalDataDlgPrivate(this)) { Q_D(EditPersonalDataDlg); d->init(title); } EditPersonalDataDlg::EditPersonalDataDlg(QString userName, QString userStreet, QString userTown, QString userCounty, QString userPostcode, QString userTelephone, QString userEmail, QWidget *parent, const QString& title) : QDialog(parent), d_ptr(new EditPersonalDataDlgPrivate(this)) { Q_D(EditPersonalDataDlg); d->init(title); d->ui->userNameEdit->setText(userName); d->ui->streetEdit->setText(userStreet); d->ui->townEdit->setText(userTown); d->ui->countyEdit->setText(userCounty); d->ui->postcodeEdit->setText(userPostcode); d->ui->telephoneEdit->setText(userTelephone); d->ui->emailEdit->setText(userEmail); } QString EditPersonalDataDlg::userName() const { Q_D(const EditPersonalDataDlg); return d->userNameText; } QString EditPersonalDataDlg::userStreet() const { Q_D(const EditPersonalDataDlg); return d->userStreetText; } QString EditPersonalDataDlg::userTown() const { Q_D(const EditPersonalDataDlg); return d->userTownText; } QString EditPersonalDataDlg::userCountry() const { Q_D(const EditPersonalDataDlg); return d->userCountyText; } QString EditPersonalDataDlg::userPostcode() const { Q_D(const EditPersonalDataDlg); return d->userPostcodeText; } QString EditPersonalDataDlg::userTelephone() const { Q_D(const EditPersonalDataDlg); return d->userTelephoneText; } QString EditPersonalDataDlg::userEmail() const { Q_D(const EditPersonalDataDlg); return d->userEmailText; } EditPersonalDataDlg::~EditPersonalDataDlg() { Q_D(EditPersonalDataDlg); delete d; } void EditPersonalDataDlg::okClicked() { Q_D(EditPersonalDataDlg); d->userNameText = d->ui->userNameEdit->text(); d->userStreetText = d->ui->streetEdit->text(); d->userTownText = d->ui->townEdit->text(); d->userCountyText = d->ui->countyEdit->text(); d->userPostcodeText = d->ui->postcodeEdit->text(); d->userTelephoneText = d->ui->telephoneEdit->text(); d->userEmailText = d->ui->emailEdit->text(); accept(); } void EditPersonalDataDlg::loadFromAddressBook() { Q_D(EditPersonalDataDlg); d->ui->userNameEdit->setText(d->m_contact->ownerFullName()); d->ui->emailEdit->setText(d->m_contact->ownerEmail()); if (d->ui->emailEdit->text().isEmpty()) { KMessageBox::sorry(this, i18n("Unable to load data, because no contact has been associated with the owner of the standard address book."), i18n("Address book import")); return; } d->ui->kabcBtn->setEnabled(false); connect(d->m_contact, &MyMoneyContact::contactFetched, this, &EditPersonalDataDlg::slotContactFetched); d->m_contact->fetchContact(d->ui->emailEdit->text()); } void EditPersonalDataDlg::slotContactFetched(const ContactData &identity) { Q_D(EditPersonalDataDlg); d->ui->telephoneEdit->setText(identity.phoneNumber); QString sep; if (!identity.country.isEmpty() && !identity.region.isEmpty()) sep = " / "; d->ui->countyEdit->setText(QString("%1%2%3").arg(identity.country, sep, identity.region)); d->ui->postcodeEdit->setText(identity.postalCode); d->ui->townEdit->setText(identity.locality); d->ui->streetEdit->setText(identity.street); d->ui->kabcBtn->setEnabled(true); } diff --git a/kmymoney/dialogs/hierarchyfilterproxymodel.h b/kmymoney/dialogs/hierarchyfilterproxymodel.h index c9c2fd7ba..5ebf6bac7 100644 --- a/kmymoney/dialogs/hierarchyfilterproxymodel.h +++ b/kmymoney/dialogs/hierarchyfilterproxymodel.h @@ -1,55 +1,55 @@ /*************************************************************************** hierarchyfilterproxymodel.h ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef HIERARCHYFILTERPROXYMODEL_H #define HIERARCHYFILTERPROXYMODEL_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Headers // ---------------------------------------------------------------------------- // Project Includes #include "accountsproxymodel.h" class HierarchyFilterProxyModel : public AccountsProxyModel { Q_OBJECT public: - HierarchyFilterProxyModel(QObject *parent = nullptr); + explicit HierarchyFilterProxyModel(QObject *parent = nullptr); Qt::ItemFlags flags(const QModelIndex &index) const override; void setCurrentAccountId(const QString &selectedAccountId); QModelIndex getSelectedParentAccountIndex() const; protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; private: QString m_currentAccountId; }; #endif diff --git a/kmymoney/dialogs/investtransactioneditor.cpp b/kmymoney/dialogs/investtransactioneditor.cpp index ca33dce95..0f8abbc1f 100644 --- a/kmymoney/dialogs/investtransactioneditor.cpp +++ b/kmymoney/dialogs/investtransactioneditor.cpp @@ -1,1223 +1,1223 @@ /*************************************************************************** investtransactioneditor.cpp ---------- begin : Fri Dec 15 2006 copyright : (C) 2006 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #include "investtransactioneditor.h" #include "transactioneditor_p.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyreconcilecombo.h" #include "kmymoneyactivitycombo.h" #include "kmymoneytagcombo.h" #include "ktagcontainer.h" #include "investtransaction.h" #include "selectedtransactions.h" #include "transactioneditorcontainer.h" #include "kmymoneycategory.h" #include "kmymoneydateinput.h" #include "kmymoneyedit.h" #include "kmymoneyaccountselector.h" #include "kmymoneymvccombo.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "ksplittransactiondlg.h" #include "kcurrencycalculator.h" #include "kmymoneyglobalsettings.h" #include "investactivities.h" #include "kmymoneycompletion.h" #include "dialogenums.h" using namespace eMyMoney; using namespace KMyMoneyRegister; using namespace KMyMoneyTransactionForm; using namespace Invest; class InvestTransactionEditorPrivate : public TransactionEditorPrivate { Q_DISABLE_COPY(InvestTransactionEditorPrivate) Q_DECLARE_PUBLIC(InvestTransactionEditor) friend class Invest::Activity; public: InvestTransactionEditorPrivate(InvestTransactionEditor* qq) : TransactionEditorPrivate(qq), m_activity(0) { m_phonyAccount = MyMoneyAccount("Phony-ID", MyMoneyAccount()); } ~InvestTransactionEditorPrivate() { delete m_activity; } void hideCategory(const QString& name) { Q_Q(InvestTransactionEditor); if (KMyMoneyCategory* cat = dynamic_cast(q->haveWidget(name))) { cat->hide(); cat->splitButton()->hide(); } } void activityFactory(eMyMoney::Split::InvestmentTransactionType type) { Q_Q(InvestTransactionEditor); if (!m_activity || type != m_activity->type()) { delete m_activity; switch (type) { default: case eMyMoney::Split::InvestmentTransactionType::BuyShares: m_activity = new Buy(q); break; case eMyMoney::Split::InvestmentTransactionType::SellShares: m_activity = new Sell(q); break; case eMyMoney::Split::InvestmentTransactionType::Dividend: case eMyMoney::Split::InvestmentTransactionType::Yield: m_activity = new Div(q); break; case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend: m_activity = new Reinvest(q); break; case eMyMoney::Split::InvestmentTransactionType::AddShares: m_activity = new Add(q); break; case eMyMoney::Split::InvestmentTransactionType::RemoveShares: m_activity = new Remove(q); break; case eMyMoney::Split::InvestmentTransactionType::SplitShares: m_activity = new Invest::Split(q); break; case eMyMoney::Split::InvestmentTransactionType::InterestIncome: m_activity = new IntInc(q); break; } } } MyMoneyMoney subtotal(const QList& splits) const { QList::const_iterator it_s; MyMoneyMoney sum; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { sum += (*it_s).value(); } return sum; } /** * This method creates a transaction to be used for the split fee/interest editor. * It has a reference to a phony account and the splits contained in @a splits . */ bool createPseudoTransaction(MyMoneyTransaction& t, const QList& splits) { t.removeSplits(); MyMoneySplit split; split.setAccountId(m_phonyAccount.id()); split.setValue(-subtotal(splits)); split.setShares(split.value()); t.addSplit(split); m_phonySplit = split; QList::const_iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { split = *it_s; split.clearId(); t.addSplit(split); } return true; } /** * Convenience method used by slotEditInterestSplits() and slotEditFeeSplits(). * * @param categoryWidgetName name of the category widget * @param amountWidgetName name of the amount widget * @param splits the splits that make up the transaction to be edited * @param isIncome @c false for fees, @c true for interest * @param slotEditSplits name of the slot to be connected to the focusIn signal of the * category widget named @p categoryWidgetName in case of multiple splits * in @p splits . */ int editSplits(const QString& categoryWidgetName, const QString& amountWidgetName, QList& splits, bool isIncome, const char* slotEditSplits) { Q_Q(InvestTransactionEditor); int rc = QDialog::Rejected; if (!m_openEditSplits) { // only get in here in a single instance m_openEditSplits = true; // force focus change to update all data KMyMoneyCategory* category = dynamic_cast(m_editWidgets[categoryWidgetName]); QWidget* w = category->splitButton(); if (w) w->setFocus(); KMyMoneyEdit* amount = dynamic_cast(q->haveWidget(amountWidgetName)); MyMoneyTransaction transaction; transaction.setCommodity(m_currency.id()); if (splits.count() == 0 && !category->selectedItem().isEmpty()) { MyMoneySplit s; s.setAccountId(category->selectedItem()); s.setShares(amount->value()); s.setValue(s.shares()); splits << s; } // use the transactions commodity as the currency indicator for the splits // this is used to allow some useful setting for the fractions in the amount fields try { m_phonyAccount.setCurrencyId(m_transaction.commodity()); m_phonyAccount.fraction(MyMoneyFile::instance()->security(m_transaction.commodity())); } catch (const MyMoneyException &) { qDebug("Unable to setup precision"); } if (createPseudoTransaction(transaction, splits)) { MyMoneyMoney value; QPointer dlg = new KSplitTransactionDlg(transaction, m_phonySplit, m_phonyAccount, false, isIncome, MyMoneyMoney(), m_priceInfo, m_regForm); // q->connect(dlg, SIGNAL(newCategory(MyMoneyAccount&)), q, SIGNAL(newCategory(MyMoneyAccount&))); if ((rc = dlg->exec()) == QDialog::Accepted) { transaction = dlg->transaction(); // collect splits out of the transaction splits.clear(); QList::const_iterator it_s; MyMoneyMoney fees; for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { if ((*it_s).accountId() == m_phonyAccount.id()) continue; splits << *it_s; fees += (*it_s).shares(); } if (isIncome) fees = -fees; QString categoryId; q->setupCategoryWidget(category, splits, categoryId, slotEditSplits); amount->setValue(fees); q->slotUpdateTotalAmount(); } delete dlg; } // focus jumps into the memo field if ((w = q->haveWidget("memo")) != 0) { w->setFocus(); } m_openEditSplits = false; } return rc; } void updatePriceMode(const MyMoneySplit& split = MyMoneySplit()) { Q_Q(InvestTransactionEditor); auto label = dynamic_cast(q->haveWidget("price-label")); if (label) { auto sharesEdit = dynamic_cast(q->haveWidget("shares")); auto priceEdit = dynamic_cast(q->haveWidget("price")); MyMoneyMoney price; if (!split.id().isEmpty()) price = split.price().reduce(); else price = priceEdit->value().abs(); if (q->priceMode() == eDialogs::PriceMode::PricePerTransaction) { priceEdit->setPrecision(m_currency.pricePrecision()); label->setText(i18n("Transaction amount")); if (!sharesEdit->value().isZero()) priceEdit->setValue(sharesEdit->value().abs() * price); } else if (q->priceMode() == eDialogs::PriceMode::PricePerShare) { priceEdit->setPrecision(m_security.pricePrecision()); label->setText(i18n("Price/Share")); priceEdit->setValue(price); } else priceEdit->setValue(price); } } Activity* m_activity; MyMoneyAccount m_phonyAccount; MyMoneySplit m_phonySplit; MyMoneySplit m_assetAccountSplit; QList m_interestSplits; QList m_feeSplits; MyMoneySecurity m_security; MyMoneySecurity m_currency; eMyMoney::Split::InvestmentTransactionType m_transactionType; }; InvestTransactionEditor::InvestTransactionEditor() : TransactionEditor(*new InvestTransactionEditorPrivate(this)) { Q_D(InvestTransactionEditor); d->m_transactionType = eMyMoney::Split::InvestmentTransactionType::UnknownTransactionType; } InvestTransactionEditor::~InvestTransactionEditor() { } InvestTransactionEditor::InvestTransactionEditor(TransactionEditorContainer* regForm, KMyMoneyRegister::InvestTransaction* item, const KMyMoneyRegister::SelectedTransactions& list, const QDate& lastPostDate) : TransactionEditor(*new InvestTransactionEditorPrivate(this), regForm, item, list, lastPostDate) { Q_D(InvestTransactionEditor); // after the gometries of the container are updated hide the widgets which are not needed by the current activity connect(d->m_regForm, &TransactionEditorContainer::geometriesUpdated, this, &InvestTransactionEditor::slotTransactionContainerGeometriesUpdated); // dissect the transaction into its type, splits, currency, security etc. KMyMoneyUtils::dissectTransaction(d->m_transaction, d->m_split, d->m_assetAccountSplit, d->m_feeSplits, d->m_interestSplits, d->m_security, d->m_currency, d->m_transactionType); // determine initial activity object d->activityFactory(d->m_transactionType); } void InvestTransactionEditor::createEditWidgets() { Q_D(InvestTransactionEditor); auto activity = new KMyMoneyActivityCombo(); d->m_editWidgets["activity"] = activity; connect(activity, &KMyMoneyActivityCombo::activitySelected, this, &InvestTransactionEditor::slotUpdateActivity); connect(activity, &KMyMoneyActivityCombo::activitySelected, this, &InvestTransactionEditor::slotUpdateButtonState); d->m_editWidgets["postdate"] = new KMyMoneyDateInput; auto security = new KMyMoneySecurity; security->setPlaceholderText(i18n("Security")); d->m_editWidgets["security"] = security; connect(security, &KMyMoneyCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateSecurity); connect(security, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(security, &KMyMoneyCombo::createItem, this, &InvestTransactionEditor::slotCreateSecurity); connect(security, &KMyMoneyCombo::objectCreation, this, &TransactionEditor::objectCreation); auto asset = new KMyMoneyCategory(false, nullptr); asset->setPlaceholderText(i18n("Asset account")); d->m_editWidgets["asset-account"] = asset; connect(asset, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(asset, &KMyMoneyCombo::objectCreation, this, &TransactionEditor::objectCreation); auto fees = new KMyMoneyCategory(true, nullptr); fees->setPlaceholderText(i18n("Fees")); d->m_editWidgets["fee-account"] = fees; connect(fees, &KMyMoneyCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateFeeCategory); connect(fees, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(fees, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateFeeVisibility); connect(fees, &KMyMoneyCombo::createItem, this, &InvestTransactionEditor::slotCreateFeeCategory); connect(fees, &KMyMoneyCombo::objectCreation, this, &TransactionEditor::objectCreation); connect(fees->splitButton(), &QAbstractButton::clicked, this, &InvestTransactionEditor::slotEditFeeSplits); auto interest = new KMyMoneyCategory(true, nullptr); interest->setPlaceholderText(i18n("Interest")); d->m_editWidgets["interest-account"] = interest; connect(interest, &KMyMoneyCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateInterestCategory); connect(interest, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(interest, &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateInterestVisibility); connect(interest, &KMyMoneyCombo::createItem, this, &InvestTransactionEditor::slotCreateInterestCategory); connect(interest, &KMyMoneyCombo::objectCreation, this, &TransactionEditor::objectCreation); connect(interest->splitButton(), &QAbstractButton::clicked, this, &InvestTransactionEditor::slotEditInterestSplits); auto tag = new KTagContainer; tag->tagCombo()->setPlaceholderText(i18n("Tag")); tag->tagCombo()->setObjectName(QLatin1String("Tag")); d->m_editWidgets["tag"] = tag; connect(tag->tagCombo(), &QComboBox::editTextChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(tag->tagCombo(), &KMyMoneyMVCCombo::createItem, this, &TransactionEditor::createTag); connect(tag->tagCombo(), &KMyMoneyMVCCombo::objectCreation, this, &TransactionEditor::objectCreation); auto memo = new KTextEdit; memo->setTabChangesFocus(true); d->m_editWidgets["memo"] = memo; connect(memo, &QTextEdit::textChanged, this, &InvestTransactionEditor::slotUpdateInvestMemoState); connect(memo, &QTextEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState); d->m_activity->memoText().clear(); d->m_activity->memoChanged() = false; KMyMoneyEdit* value = new KMyMoneyEdit; value->setPlaceholderText(i18n("Shares")); value->setResetButtonVisible(false); d->m_editWidgets["shares"] = value; connect(value, &KMyMoneyEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(value, &KMyMoneyEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount); value = new KMyMoneyEdit; value->setPlaceholderText(i18n("Price")); value->setResetButtonVisible(false); d->m_editWidgets["price"] = value; connect(value, &KMyMoneyEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(value, &KMyMoneyEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount); value = new KMyMoneyEdit; // TODO once we have the selected transactions as array of Transaction // we can allow multiple splits for fee and interest value->setResetButtonVisible(false); d->m_editWidgets["fee-amount"] = value; connect(value, &KMyMoneyEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(value, &KMyMoneyEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount); value = new KMyMoneyEdit; // TODO once we have the selected transactions as array of Transaction // we can allow multiple splits for fee and interest value->setResetButtonVisible(false); d->m_editWidgets["interest-amount"] = value; connect(value, &KMyMoneyEdit::textChanged, this, &InvestTransactionEditor::slotUpdateButtonState); connect(value, &KMyMoneyEdit::valueChanged, this, &InvestTransactionEditor::slotUpdateTotalAmount); auto reconcile = new KMyMoneyReconcileCombo; d->m_editWidgets["status"] = reconcile; connect(reconcile, &KMyMoneyMVCCombo::itemSelected, this, &InvestTransactionEditor::slotUpdateButtonState); KMyMoneyRegister::QWidgetContainer::iterator it_w; for (it_w = d->m_editWidgets.begin(); it_w != d->m_editWidgets.end(); ++it_w) { (*it_w)->installEventFilter(this); } QLabel* label; d->m_editWidgets["activity-label"] = label = new QLabel(i18n("Activity")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["postdate-label"] = label = new QLabel(i18n("Date")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["security-label"] = label = new QLabel(i18n("Security")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["shares-label"] = label = new QLabel(i18n("Shares")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["asset-label"] = label = new QLabel(i18n("Account")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["price-label"] = label = new QLabel(i18n("Price/share")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["fee-label"] = label = new QLabel(i18n("Fees")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["fee-amount-label"] = label = new QLabel(""); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["interest-label"] = label = new QLabel(i18n("Interest")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["interest-amount-label"] = label = new QLabel(i18n("Interest")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["memo-label"] = label = new QLabel(i18n("Memo")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["total"] = label = new QLabel(""); label->setAlignment(Qt::AlignVCenter | Qt::AlignRight); d->m_editWidgets["total-label"] = label = new QLabel(i18nc("Total value", "Total")); label->setAlignment(Qt::AlignVCenter); d->m_editWidgets["status-label"] = label = new QLabel(i18n("Status")); label->setAlignment(Qt::AlignVCenter); // if we don't have more than 1 selected transaction, we don't need // the "don't change" item in some of the combo widgets if (d->m_transactions.count() < 2) { reconcile->removeDontCare(); } } int InvestTransactionEditor::slotEditFeeSplits() { Q_D(InvestTransactionEditor); return d->editSplits("fee-account", "fee-amount", d->m_feeSplits, false, SLOT(slotEditFeeSplits())); } int InvestTransactionEditor::slotEditInterestSplits() { Q_D(InvestTransactionEditor); return d->editSplits("interest-account", "interest-amount", d->m_interestSplits, true, SLOT(slotEditInterestSplits())); } void InvestTransactionEditor::slotCreateSecurity(const QString& name, QString& id) { Q_D(InvestTransactionEditor); MyMoneyAccount acc; QRegExp exp("([^:]+)"); if (exp.indexIn(name) != -1) { acc.setName(exp.cap(1)); emit createSecurity(acc, d->m_account); // return id id = acc.id(); if (!id.isEmpty()) { slotUpdateSecurity(id); } } } void InvestTransactionEditor::slotCreateFeeCategory(const QString& name, QString& id) { MyMoneyAccount acc; acc.setName(name); emit createCategory(acc, MyMoneyFile::instance()->expense()); // return id id = acc.id(); } void InvestTransactionEditor::slotUpdateFeeCategory(const QString& id) { haveWidget("fee-amount")->setDisabled(id.isEmpty()); } void InvestTransactionEditor::slotUpdateFeeVisibility(const QString& txt) { Q_D(InvestTransactionEditor); static const QSet transactionTypesWithoutFee = QSet() << eMyMoney::Split::InvestmentTransactionType::AddShares << eMyMoney::Split::InvestmentTransactionType::RemoveShares << eMyMoney::Split::InvestmentTransactionType::SplitShares; KMyMoneyEdit* feeAmount = dynamic_cast(haveWidget("fee-amount")); feeAmount->setHidden(txt.isEmpty()); QLabel* l = dynamic_cast(haveWidget("fee-amount-label")); KMyMoneyCategory* fee = dynamic_cast(haveWidget("fee-account")); const bool hideFee = txt.isEmpty() || transactionTypesWithoutFee.contains(d->m_activity->type()); // no fee expected so hide if (hideFee) { if (l) { l->setText(""); } feeAmount->hide(); fee->splitButton()->hide(); } else { if (l) { l->setText(i18n("Fee Amount")); } feeAmount->show(); fee->splitButton()->show(); } } void InvestTransactionEditor::slotUpdateInterestCategory(const QString& id) { haveWidget("interest-amount")->setDisabled(id.isEmpty()); } void InvestTransactionEditor::slotUpdateInterestVisibility(const QString& txt) { Q_D(InvestTransactionEditor); static const QSet transactionTypesWithInterest = QSet() << eMyMoney::Split::InvestmentTransactionType::BuyShares << eMyMoney::Split::InvestmentTransactionType::SellShares << eMyMoney::Split::InvestmentTransactionType::Dividend << eMyMoney::Split::InvestmentTransactionType::InterestIncome << eMyMoney::Split::InvestmentTransactionType::Yield; QWidget* w = haveWidget("interest-amount"); w->setHidden(txt.isEmpty()); QLabel* l = dynamic_cast(haveWidget("interest-amount-label")); KMyMoneyCategory* interest = dynamic_cast(haveWidget("interest-account")); const bool showInterest = !txt.isEmpty() && transactionTypesWithInterest.contains(d->m_activity->type()); if (interest && showInterest) { interest->splitButton()->show(); w->show(); if (l) l->setText(i18n("Interest")); } else { if (interest) { interest->splitButton()->hide(); w->hide(); if (l) l->setText(QString()); } } } void InvestTransactionEditor::slotCreateInterestCategory(const QString& name, QString& id) { MyMoneyAccount acc; acc.setName(name); emit createCategory(acc, MyMoneyFile::instance()->income()); id = acc.id(); } void InvestTransactionEditor::slotReloadEditWidgets() { Q_D(InvestTransactionEditor); auto interest = dynamic_cast(haveWidget("interest-account")); auto fees = dynamic_cast(haveWidget("fee-account")); auto security = dynamic_cast(haveWidget("security")); AccountSet aSet; QString id; // interest-account aSet.clear(); aSet.addAccountGroup(Account::Type::Income); aSet.load(interest->selector()); setupCategoryWidget(interest, d->m_interestSplits, id, SLOT(slotEditInterestSplits())); // fee-account aSet.clear(); aSet.addAccountGroup(Account::Type::Expense); aSet.load(fees->selector()); setupCategoryWidget(fees, d->m_feeSplits, id, SLOT(slotEditFeeSplits())); // security aSet.clear(); aSet.load(security->selector(), i18n("Security"), d->m_account.accountList(), true); } void InvestTransactionEditor::loadEditWidgets(eWidgets::eRegister::Action) { loadEditWidgets(); } void InvestTransactionEditor::loadEditWidgets() { Q_D(InvestTransactionEditor); QString id; auto postDate = dynamic_cast(haveWidget("postdate")); auto reconcile = dynamic_cast(haveWidget("status")); auto security = dynamic_cast(haveWidget("security")); auto activity = dynamic_cast(haveWidget("activity")); auto asset = dynamic_cast(haveWidget("asset-account")); auto memo = dynamic_cast(d->m_editWidgets["memo"]); KMyMoneyEdit* value; auto interest = dynamic_cast(haveWidget("interest-account")); auto fees = dynamic_cast(haveWidget("fee-account")); // check if the current transaction has a reference to an equity account bool haveEquityAccount = false; QList::const_iterator it_s; for (it_s = d->m_transaction.splits().constBegin(); !haveEquityAccount && it_s != d->m_transaction.splits().constEnd(); ++it_s) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (acc.accountType() == Account::Type::Equity) haveEquityAccount = true; } // asset-account AccountSet aSet; aSet.clear(); aSet.addAccountType(Account::Type::Checkings); aSet.addAccountType(Account::Type::Savings); aSet.addAccountType(Account::Type::Cash); aSet.addAccountType(Account::Type::Asset); aSet.addAccountType(Account::Type::Currency); aSet.addAccountType(Account::Type::CreditCard); if (KMyMoneyGlobalSettings::expertMode() || haveEquityAccount) aSet.addAccountGroup(Account::Type::Equity); aSet.load(asset->selector()); // security security->setSuppressObjectCreation(false); // allow object creation on the fly aSet.clear(); aSet.load(security->selector(), i18n("Security"), d->m_account.accountList(), true); // memo memo->setText(d->m_split.memo()); d->m_activity->memoText() = d->m_split.memo(); d->m_activity->memoChanged() = false; if (!isMultiSelection()) { // date if (d->m_transaction.postDate().isValid()) postDate->setDate(d->m_transaction.postDate()); else if (d->m_lastPostDate.isValid()) postDate->setDate(d->m_lastPostDate); else postDate->setDate(QDate::currentDate()); // security (but only if it's not the investment account) if (d->m_split.accountId() != d->m_account.id()) { security->completion()->setSelected(d->m_split.accountId()); security->slotItemSelected(d->m_split.accountId()); } // activity activity->setActivity(d->m_activity->type()); slotUpdateActivity(activity->activity()); asset->completion()->setSelected(d->m_assetAccountSplit.accountId()); asset->slotItemSelected(d->m_assetAccountSplit.accountId()); // interest-account aSet.clear(); aSet.addAccountGroup(Account::Type::Income); aSet.load(interest->selector()); setupCategoryWidget(interest, d->m_interestSplits, id, SLOT(slotEditInterestSplits())); slotUpdateInterestVisibility(interest->currentText()); // fee-account aSet.clear(); aSet.addAccountGroup(Account::Type::Expense); aSet.load(fees->selector()); setupCategoryWidget(fees, d->m_feeSplits, id, SLOT(slotEditFeeSplits())); slotUpdateFeeVisibility(fees->currentText()); // shares // don't set the value if the number of shares is zero so that // we can see the hint value = dynamic_cast(haveWidget("shares")); if (typeid(*(d->m_activity)) != typeid(Invest::Split(this))) value->setPrecision(MyMoneyMoney::denomToPrec(d->m_security.smallestAccountFraction())); else value->setPrecision(-1); if (!d->m_split.shares().isZero()) value->setValue(d->m_split.shares().abs()); // price d->updatePriceMode(d->m_split); // fee amount value = dynamic_cast(haveWidget("fee-amount")); value->setValue(d->subtotal(d->m_feeSplits)); // interest amount value = dynamic_cast(haveWidget("interest-amount")); value->setValue(-d->subtotal(d->m_interestSplits)); // total slotUpdateTotalAmount(); // status if (d->m_split.reconcileFlag() == eMyMoney::Split::State::Unknown) d->m_split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); reconcile->setState(d->m_split.reconcileFlag()); } else { postDate->loadDate(QDate()); reconcile->setState(eMyMoney::Split::State::Unknown); // We don't allow to change the activity activity->setActivity(d->m_activity->type()); slotUpdateActivity(activity->activity()); activity->setDisabled(true); // scan the list of selected transactions and check that they have // the same activity. KMyMoneyRegister::SelectedTransactions::iterator it_t = d->m_transactions.begin(); const QString& action = d->m_item->split().action(); bool isNegative = d->m_item->split().shares().isNegative(); bool allSameActivity = true; for (it_t = d->m_transactions.begin(); allSameActivity && (it_t != d->m_transactions.end()); ++it_t) { allSameActivity = (action == (*it_t).split().action() && (*it_t).split().shares().isNegative() == isNegative); } QStringList fields; fields << "shares" << "price" << "fee-amount" << "interest-amount"; QStringList::const_iterator it_f; for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) { value = dynamic_cast(haveWidget((*it_f))); value->setText(""); value->setAllowEmpty(); } // if we have transactions with different activities, disable some more widgets if (!allSameActivity) { fields << "asset-account" << "fee-account" << "interest-account"; QStringList::const_iterator it_f; for (it_f = fields.constBegin(); it_f != fields.constEnd(); ++it_f) { haveWidget(*it_f)->setDisabled(true); } } } } QWidget* InvestTransactionEditor::firstWidget() const { return nullptr; // let the creator use the first widget in the tab order } bool InvestTransactionEditor::isComplete(QString& reason) const { Q_D(const InvestTransactionEditor); reason.clear(); return d->m_activity->isComplete(reason); } void InvestTransactionEditor::slotUpdateSecurity(const QString& stockId) { Q_D(InvestTransactionEditor); auto file = MyMoneyFile::instance(); MyMoneyAccount stock = file->account(stockId); d->m_security = file->security(stock.currencyId()); d->m_currency = file->security(d->m_security.tradingCurrency()); bool currencyKnown = !d->m_currency.id().isEmpty(); if (!currencyKnown) { d->m_currency.setTradingSymbol("???"); } else { if (typeid(*(d->m_activity)) != typeid(Invest::Split(this))) { dynamic_cast(haveWidget("shares"))->setPrecision(MyMoneyMoney::denomToPrec(d->m_security.smallestAccountFraction())); } else { dynamic_cast(haveWidget("shares"))->setPrecision(-1); } } d->updatePriceMode(); d->m_activity->preloadAssetAccount(); haveWidget("shares")->setEnabled(currencyKnown); haveWidget("price")->setEnabled(currencyKnown); haveWidget("fee-amount")->setEnabled(currencyKnown); haveWidget("interest-amount")->setEnabled(currencyKnown); slotUpdateTotalAmount(); slotUpdateButtonState(); resizeForm(); } bool InvestTransactionEditor::fixTransactionCommodity(const MyMoneyAccount& /* account */) { return true; } void InvestTransactionEditor::totalAmount(MyMoneyMoney& amount) const { auto activityCombo = dynamic_cast(haveWidget("activity")); auto sharesEdit = dynamic_cast(haveWidget("shares")); auto priceEdit = dynamic_cast(haveWidget("price")); auto feesEdit = dynamic_cast(haveWidget("fee-amount")); auto interestEdit = dynamic_cast(haveWidget("interest-amount")); if (priceMode() == eDialogs::PriceMode::PricePerTransaction) amount = priceEdit->value().abs(); else amount = sharesEdit->value().abs() * priceEdit->value().abs(); if (feesEdit->isVisible()) { MyMoneyMoney fee = feesEdit->value(); MyMoneyMoney factor(-1, 1); switch (activityCombo->activity()) { case eMyMoney::Split::InvestmentTransactionType::BuyShares: case eMyMoney::Split::InvestmentTransactionType::ReinvestDividend: factor = MyMoneyMoney::ONE; break; default: break; } amount += (fee * factor); } if (interestEdit->isVisible()) { MyMoneyMoney interest = interestEdit->value(); MyMoneyMoney factor(1, 1); switch (activityCombo->activity()) { case eMyMoney::Split::InvestmentTransactionType::BuyShares: factor = MyMoneyMoney::MINUS_ONE; break; default: break; } amount += (interest * factor); } } void InvestTransactionEditor::slotUpdateTotalAmount() { Q_D(InvestTransactionEditor); QLabel* total = dynamic_cast(haveWidget("total")); if (total && total->isVisible()) { MyMoneyMoney amount; totalAmount(amount); total->setText(amount.convert(d->m_currency.smallestAccountFraction(), static_cast(d->m_security.roundingMethod())) .formatMoney(d->m_currency.tradingSymbol(), MyMoneyMoney::denomToPrec(d->m_currency.smallestAccountFraction()))); } } void InvestTransactionEditor::slotTransactionContainerGeometriesUpdated() { Q_D(InvestTransactionEditor); // when the geometries of the transaction container are updated some edit widgets that were // previously hidden are being shown (see QAbstractItemView::updateEditorGeometries) so we // need to update the activity with the current activity in order to show only the widgets // which are needed by the current activity if (d->m_editWidgets.isEmpty()) return; slotUpdateActivity(d->m_activity->type()); } void InvestTransactionEditor::slotUpdateActivity(eMyMoney::Split::InvestmentTransactionType activity) { Q_D(InvestTransactionEditor); // create new activity object if required d->activityFactory(activity); // hide all dynamic widgets d->hideCategory("interest-account"); d->hideCategory("fee-account"); QStringList dynwidgets; dynwidgets << "total-label" << "asset-label" << "fee-label" << "fee-amount-label" << "interest-label" << "interest-amount-label" << "price-label" << "shares-label"; // hiding labels works by clearing them. hide() does not do the job // as the underlying text in the QTable object will shine through QStringList::const_iterator it_s; for (it_s = dynwidgets.constBegin(); it_s != dynwidgets.constEnd(); ++it_s) { QLabel* w = dynamic_cast(haveWidget(*it_s)); if (w) w->setText(" "); } // real widgets can be hidden dynwidgets.clear(); dynwidgets << "asset-account" << "interest-amount" << "fee-amount" << "shares" << "price" << "total"; for (it_s = dynwidgets.constBegin(); it_s != dynwidgets.constEnd(); ++it_s) { QWidget* w = haveWidget(*it_s); if (w) w->hide(); } d->m_activity->showWidgets(); d->m_activity->preloadAssetAccount(); if (KMyMoneyCategory* cat = dynamic_cast(haveWidget("interest-account"))) { if (cat->parentWidget()->isVisible()) slotUpdateInterestVisibility(cat->currentText()); else cat->splitButton()->hide(); } if (KMyMoneyCategory* cat = dynamic_cast(haveWidget("fee-account"))) { if (cat->parentWidget()->isVisible()) slotUpdateFeeVisibility(cat->currentText()); else cat->splitButton()->hide(); } } eDialogs::PriceMode InvestTransactionEditor::priceMode() const { Q_D(const InvestTransactionEditor); eDialogs::PriceMode mode = static_cast(eDialogs::PriceMode::Price); KMyMoneySecurity* sec = dynamic_cast(d->m_editWidgets["security"]); QString accId; if (!sec->currentText().isEmpty()) { accId = sec->selectedItem(); if (accId.isEmpty()) accId = d->m_account.id(); } while (!accId.isEmpty() && mode == eDialogs::PriceMode::Price) { MyMoneyAccount acc = MyMoneyFile::instance()->account(accId); if (acc.value("priceMode").isEmpty()) accId = acc.parentAccountId(); else mode = static_cast(acc.value("priceMode").toInt()); } // if mode is still then use that if (mode == eDialogs::PriceMode::Price) mode = eDialogs::PriceMode::PricePerShare; return mode; } MyMoneySecurity InvestTransactionEditor::security() const { Q_D(const InvestTransactionEditor); return d->m_security; } QList InvestTransactionEditor::feeSplits() const { Q_D(const InvestTransactionEditor); return d->m_feeSplits; } QList InvestTransactionEditor::interestSplits() const { Q_D(const InvestTransactionEditor); return d->m_interestSplits; } bool InvestTransactionEditor::setupPrice(const MyMoneyTransaction& t, MyMoneySplit& split) { Q_D(InvestTransactionEditor); auto file = MyMoneyFile::instance(); MyMoneyAccount acc = file->account(split.accountId()); MyMoneySecurity toCurrency(file->security(acc.currencyId())); int fract = acc.fraction(); if (acc.currencyId() != t.commodity()) { if (acc.currencyId().isEmpty()) acc.setCurrencyId(t.commodity()); QMap::Iterator it_p; QString key = t.commodity() + '-' + acc.currencyId(); it_p = d->m_priceInfo.find(key); // if it's not found, then collect it from the user first MyMoneyMoney price; if (it_p == d->m_priceInfo.end()) { MyMoneySecurity fromCurrency = file->security(t.commodity()); MyMoneyMoney fromValue, toValue; fromValue = split.value(); const MyMoneyPrice &priceInfo = MyMoneyFile::instance()->price(fromCurrency.id(), toCurrency.id(), t.postDate()); toValue = split.value() * priceInfo.rate(toCurrency.id()); QPointer calc = new KCurrencyCalculator(fromCurrency, toCurrency, fromValue, toValue, t.postDate(), fract, d->m_regForm); if (calc->exec() == QDialog::Rejected) { delete calc; return false; } price = calc->price(); delete calc; d->m_priceInfo[key] = price; } else { price = (*it_p); } // update shares if the transaction commodity is the currency // of the current selected account split.setShares(split.value() * price); } else { split.setShares(split.value()); } return true; } bool InvestTransactionEditor::createTransaction(MyMoneyTransaction& t, const MyMoneyTransaction& torig, const MyMoneySplit& sorig, bool /* skipPriceDialog */) { Q_D(InvestTransactionEditor); auto file = MyMoneyFile::instance(); // we start with the previous values, make sure we can add them later on t = torig; MyMoneySplit s0 = sorig; s0.clearId(); KMyMoneySecurity* sec = dynamic_cast(d->m_editWidgets["security"]); if (!isMultiSelection() || (isMultiSelection() && !sec->currentText().isEmpty())) { QString securityId = sec->selectedItem(); if (!securityId.isEmpty()) { s0.setAccountId(securityId); MyMoneyAccount stockAccount = file->account(securityId); QString currencyId = stockAccount.currencyId(); MyMoneySecurity security = file->security(currencyId); t.setCommodity(security.tradingCurrency()); } else { s0.setAccountId(d->m_account.id()); t.setCommodity(d->m_account.currencyId()); } } // extract price info from original transaction d->m_priceInfo.clear(); QList::const_iterator it_s; if (!torig.id().isEmpty()) { for (it_s = torig.splits().begin(); it_s != torig.splits().end(); ++it_s) { if ((*it_s).id() != sorig.id()) { MyMoneyAccount cat = file->account((*it_s).accountId()); if (cat.currencyId() != d->m_account.currencyId()) { if (cat.currencyId().isEmpty()) cat.setCurrencyId(d->m_account.currencyId()); if (!(*it_s).shares().isZero() && !(*it_s).value().isZero()) { d->m_priceInfo[cat.currencyId()] = ((*it_s).shares() / (*it_s).value()).reduce(); } } } } } t.removeSplits(); KMyMoneyDateInput* postDate = dynamic_cast(d->m_editWidgets["postdate"]); if (postDate->date().isValid()) { t.setPostDate(postDate->date()); } // memo and number field are special: if we have multiple transactions selected // and the edit field is empty, we treat it as "not modified". // FIXME a better approach would be to have a 'dirty' flag with the widgets // which identifies if the originally loaded value has been modified // by the user KTextEdit* memo = dynamic_cast(d->m_editWidgets["memo"]); if (memo) { if (!isMultiSelection() || (isMultiSelection() && d->m_activity->memoChanged())) s0.setMemo(memo->toPlainText()); } MyMoneySplit assetAccountSplit; QList feeSplits; QList interestSplits; MyMoneySecurity security, currency; eMyMoney::Split::InvestmentTransactionType transactionType; // extract the splits from the original transaction KMyMoneyUtils::dissectTransaction(torig, sorig, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType); // check if the trading currency is the same if the security has changed // in case it differs, check that we have a price (request from user) // and convert all splits // TODO // do the conversions here // TODO // keep the current activity object and create a new one // that can be destroyed later on auto activity = d->m_activity; d->m_activity = 0; // make sure we create a new one d->activityFactory(activity->type()); // if the activity is not set in the combo widget, we keep // the one which is used in the original transaction auto activityCombo = dynamic_cast(haveWidget("activity")); if (activityCombo->activity() == eMyMoney::Split::InvestmentTransactionType::UnknownTransactionType) { d->activityFactory(transactionType); } // if we mark the split reconciled here, we'll use today's date if no reconciliation date is given auto status = dynamic_cast(d->m_editWidgets["status"]); if (status->state() != eMyMoney::Split::State::Unknown) s0.setReconcileFlag(status->state()); if (s0.reconcileFlag() == eMyMoney::Split::State::Reconciled && !s0.reconcileDate().isValid()) s0.setReconcileDate(QDate::currentDate()); // call the creation logic for the current selected activity bool rc = d->m_activity->createTransaction(t, s0, assetAccountSplit, feeSplits, d->m_feeSplits, interestSplits, d->m_interestSplits, security, currency); // now switch back to the original activity delete d->m_activity; d->m_activity = activity; // add the splits to the transaction if (rc) { if (security.name().isEmpty()) // new transaction has no security filled... security = file->security(file->account(s0.accountId()).currencyId()); // ...so fetch it from s0 split QList resultSplits; // concatenates splits for easy processing if (!assetAccountSplit.accountId().isEmpty()) resultSplits.append(assetAccountSplit); if (!feeSplits.isEmpty()) resultSplits.append(feeSplits); if (!interestSplits.isEmpty()) resultSplits.append(interestSplits); AlkValue::RoundingMethod roundingMethod = AlkValue::RoundRound; if (security.roundingMethod() != AlkValue::RoundNever) roundingMethod = security.roundingMethod(); int currencyFraction = currency.smallestAccountFraction(); int securityFraction = security.smallestAccountFraction(); // assuming that all non-stock splits are monetary foreach (auto split, resultSplits) { split.clearId(); - split.setShares(split.shares().convertDenominator(currencyFraction, roundingMethod)); - split.setValue(split.value().convertDenominator(currencyFraction, roundingMethod)); + split.setShares(MyMoneyMoney(split.shares().convertDenominator(currencyFraction, roundingMethod))); + split.setValue(MyMoneyMoney(split.value().convertDenominator(currencyFraction, roundingMethod))); t.addSplit(split); } - s0.setShares(s0.shares().convertDenominator(securityFraction, roundingMethod)); // only shares variable from stock split isn't evaluated in currency - s0.setValue(s0.value().convertDenominator(currencyFraction, roundingMethod)); + s0.setShares(MyMoneyMoney(s0.shares().convertDenominator(securityFraction, roundingMethod))); // only shares variable from stock split isn't evaluated in currency + s0.setValue(MyMoneyMoney(s0.value().convertDenominator(currencyFraction, roundingMethod))); t.addSplit(s0); } return rc; } void InvestTransactionEditor::setupFinalWidgets() { addFinalWidget(haveWidget("memo")); } void InvestTransactionEditor::slotUpdateInvestMemoState() { Q_D(InvestTransactionEditor); auto memo = dynamic_cast(d->m_editWidgets["memo"]); if (memo) { d->m_activity->memoChanged() = (memo->toPlainText() != d->m_activity->memoText()); } } diff --git a/kmymoney/dialogs/kavailablecurrencydlg.h b/kmymoney/dialogs/kavailablecurrencydlg.h index 8d060fca2..dd93c7dd0 100644 --- a/kmymoney/dialogs/kavailablecurrencydlg.h +++ b/kmymoney/dialogs/kavailablecurrencydlg.h @@ -1,54 +1,54 @@ /*************************************************************************** kavailablecurrencydlg.h - description ------------------- begin : Sat Apr 01 2017 copyright : (C) 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. * * * ***************************************************************************/ #ifndef KAVAILABLECURRENCYDLG_H #define KAVAILABLECURRENCYDLG_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes namespace Ui { class KAvailableCurrencyDlg; } class KTreeWidgetSearchLineWidget; class KAvailableCurrencyDlg : public QDialog { Q_OBJECT public: - KAvailableCurrencyDlg(QWidget *parent = nullptr); + explicit KAvailableCurrencyDlg(QWidget *parent = nullptr); ~KAvailableCurrencyDlg(); Ui::KAvailableCurrencyDlg* ui; protected slots: void slotLoadCurrencies(); void slotItemSelectionChanged(); private: KTreeWidgetSearchLineWidget* m_searchWidget; }; #endif diff --git a/kmymoney/dialogs/kcurrencycalculator.cpp b/kmymoney/dialogs/kcurrencycalculator.cpp index 2f2744949..06d1f9f38 100644 --- a/kmymoney/dialogs/kcurrencycalculator.cpp +++ b/kmymoney/dialogs/kcurrencycalculator.cpp @@ -1,385 +1,385 @@ /*************************************************************************** kcurrencycalculator.cpp - description ------------------- begin : Thu Apr 8 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Ł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. * * * ***************************************************************************/ #include "kcurrencycalculator.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kcurrencycalculator.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "kmymoneyedit.h" #include "kmymoneydateinput.h" #include "mymoneyprice.h" #include "mymoneymoney.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "kmymoneyglobalsettings.h" class KCurrencyCalculatorPrivate { Q_DISABLE_COPY(KCurrencyCalculatorPrivate) Q_DECLARE_PUBLIC(KCurrencyCalculator) public: - KCurrencyCalculatorPrivate(KCurrencyCalculator *qq, - const MyMoneySecurity& from, - const MyMoneySecurity& to, - const MyMoneyMoney& value, - const MyMoneyMoney& shares, - const QDate& date, - const signed64 resultFraction) : + explicit KCurrencyCalculatorPrivate(KCurrencyCalculator *qq, + const MyMoneySecurity& from, + const MyMoneySecurity& to, + const MyMoneyMoney& value, + const MyMoneyMoney& shares, + const QDate& date, + const signed64 resultFraction) : q_ptr(qq), ui(new Ui::KCurrencyCalculator), m_fromCurrency(from), m_toCurrency(to), m_result(shares.abs()), m_value(value.abs()), m_date(date), m_resultFraction(resultFraction) { } ~KCurrencyCalculatorPrivate() { delete ui; } void init() { Q_Q(KCurrencyCalculator); ui->setupUi(q); auto file = MyMoneyFile::instance(); //set main widget of QDialog ui->buttonGroup1->setId(ui->m_amountButton, 0); ui->buttonGroup1->setId(ui->m_rateButton, 1); ui->m_dateFrame->hide(); if (m_date.isValid()) ui->m_dateEdit->setDate(m_date); else ui->m_dateEdit->setDate(QDate::currentDate()); ui->m_fromCurrencyText->setText(QString(MyMoneySecurity::securityTypeToString(m_fromCurrency.securityType()) + ' ' + (m_fromCurrency.isCurrency() ? m_fromCurrency.id() : m_fromCurrency.tradingSymbol()))); ui->m_toCurrencyText->setText(QString(MyMoneySecurity::securityTypeToString(m_toCurrency.securityType()) + ' ' + (m_toCurrency.isCurrency() ? m_toCurrency.id() : m_toCurrency.tradingSymbol()))); //set bold font auto boldFont = ui->m_fromCurrencyText->font(); boldFont.setBold(true); ui->m_fromCurrencyText->setFont(boldFont); boldFont = ui->m_toCurrencyText->font(); boldFont.setBold(true); ui->m_toCurrencyText->setFont(boldFont); ui->m_fromAmount->setText(m_value.formatMoney(QString(), MyMoneyMoney::denomToPrec(m_fromCurrency.smallestAccountFraction()))); ui->m_dateText->setText(QLocale().toString(m_date)); ui->m_updateButton->setChecked(KMyMoneyGlobalSettings::priceHistoryUpdate()); // setup initial result if (m_result == MyMoneyMoney() && !m_value.isZero()) { const MyMoneyPrice &pr = file->price(m_fromCurrency.id(), m_toCurrency.id(), m_date); if (pr.isValid()) { m_result = m_value * pr.rate(m_toCurrency.id()); } } // fill in initial values ui->m_toAmount->loadText(m_result.formatMoney(QString(), MyMoneyMoney::denomToPrec(m_resultFraction))); ui->m_toAmount->setPrecision(MyMoneyMoney::denomToPrec(m_resultFraction)); ui->m_conversionRate->setPrecision(m_fromCurrency.pricePrecision()); q->connect(ui->m_amountButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetToAmount); q->connect(ui->m_rateButton, &QAbstractButton::clicked, q, &KCurrencyCalculator::slotSetExchangeRate); q->connect(ui->m_toAmount, &KMyMoneyEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateResult); q->connect(ui->m_conversionRate, &KMyMoneyEdit::valueChanged, q, &KCurrencyCalculator::slotUpdateRate); // use this as the default ui->m_amountButton->animateClick(); q->slotUpdateResult(ui->m_toAmount->text()); // If the from security is not a currency, we only allow entering a price if (!m_fromCurrency.isCurrency()) { ui->m_rateButton->animateClick(); ui->m_amountButton->hide(); ui->m_toAmount->hide(); } } void updateExample(const MyMoneyMoney& price) { QString msg; if (price.isZero()) { msg = QString("1 %1 = ? %2").arg(m_fromCurrency.tradingSymbol()) .arg(m_toCurrency.tradingSymbol()); if (m_fromCurrency.isCurrency()) { msg += QString("\n"); msg += QString("1 %1 = ? %2").arg(m_toCurrency.tradingSymbol()) .arg(m_fromCurrency.tradingSymbol()); } } else { msg = QString("1 %1 = %2 %3").arg(m_fromCurrency.tradingSymbol()) .arg(price.formatMoney(QString(), m_fromCurrency.pricePrecision())) .arg(m_toCurrency.tradingSymbol()); if (m_fromCurrency.isCurrency()) { msg += QString("\n"); msg += QString("1 %1 = %2 %3").arg(m_toCurrency.tradingSymbol()) .arg((MyMoneyMoney::ONE / price).formatMoney(QString(), m_toCurrency.pricePrecision())) .arg(m_fromCurrency.tradingSymbol()); } } ui->m_conversionExample->setText(msg); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!price.isZero()); } KCurrencyCalculator *q_ptr; Ui::KCurrencyCalculator *ui; MyMoneySecurity m_fromCurrency; MyMoneySecurity m_toCurrency; MyMoneyMoney m_result; MyMoneyMoney m_value; QDate m_date; signed64 m_resultFraction; }; KCurrencyCalculator::KCurrencyCalculator(const MyMoneySecurity& from, const MyMoneySecurity& to, const MyMoneyMoney& value, const MyMoneyMoney& shares, const QDate& date, const signed64 resultFraction, QWidget *parent) : QDialog(parent), d_ptr(new KCurrencyCalculatorPrivate(this, from, to, value, shares, date, resultFraction)) { Q_D(KCurrencyCalculator); d->init(); } KCurrencyCalculator::~KCurrencyCalculator() { Q_D(KCurrencyCalculator); delete d; } bool KCurrencyCalculator::setupSplitPrice(MyMoneyMoney& shares, const MyMoneyTransaction& t, const MyMoneySplit& s, const QMap& priceInfo, QWidget* parentWidget) { auto rc = true; auto file = MyMoneyFile::instance(); if (!s.value().isZero()) { auto cat = file->account(s.accountId()); MyMoneySecurity toCurrency; toCurrency = file->security(cat.currencyId()); // determine the fraction required for this category/account int fract = cat.fraction(toCurrency); if (cat.currencyId() != t.commodity()) { MyMoneyMoney toValue; auto fromCurrency = file->security(t.commodity()); // display only positive values to the user auto fromValue = s.value().abs(); // if we had a price info in the beginning, we use it here if (priceInfo.find(cat.currencyId()) != priceInfo.end()) { toValue = (fromValue * priceInfo[cat.currencyId()]).convert(fract); } // if the shares are still 0, we need to change that if (toValue.isZero()) { const MyMoneyPrice &price = file->price(fromCurrency.id(), toCurrency.id(), t.postDate()); // if the price is valid calculate the shares. If it is invalid // assume a conversion rate of 1.0 if (price.isValid()) { toValue = (price.rate(toCurrency.id()) * fromValue).convert(fract); } else { toValue = fromValue; } } // now present all that to the user QPointer calc = new KCurrencyCalculator(fromCurrency, toCurrency, fromValue, toValue, t.postDate(), fract, parentWidget); if (calc->exec() == QDialog::Rejected) { rc = false; } else shares = (s.value() * calc->price()).convert(fract); delete calc; } else { shares = s.value().convert(fract); } } else shares = s.value(); return rc; } void KCurrencyCalculator::setupPriceEditor() { Q_D(KCurrencyCalculator); d->ui->m_dateFrame->show(); d->ui->m_amountDateFrame->hide(); d->ui->m_updateButton->setChecked(true); d->ui->m_updateButton->hide(); } void KCurrencyCalculator::slotSetToAmount() { Q_D(KCurrencyCalculator); d->ui->m_rateButton->setChecked(false); d->ui->m_toAmount->setEnabled(true); d->ui->m_conversionRate->setEnabled(false); } void KCurrencyCalculator::slotSetExchangeRate() { Q_D(KCurrencyCalculator); d->ui->m_amountButton->setChecked(false); d->ui->m_toAmount->setEnabled(false); d->ui->m_conversionRate->setEnabled(true); } void KCurrencyCalculator::slotUpdateResult(const QString& /*txt*/) { Q_D(KCurrencyCalculator); MyMoneyMoney result = d->ui->m_toAmount->value(); MyMoneyMoney price(0, 1); if (result.isNegative()) { d->ui->m_toAmount->setValue(-result); slotUpdateResult(QString()); return; } if (!result.isZero()) { price = result / d->m_value; d->ui->m_conversionRate->loadText(price.formatMoney(QString(), d->m_fromCurrency.pricePrecision())); d->m_result = (d->m_value * price).convert(d->m_resultFraction); d->ui->m_toAmount->loadText(d->m_result.formatMoney(d->m_resultFraction)); } d->updateExample(price); } void KCurrencyCalculator::slotUpdateRate(const QString& /*txt*/) { Q_D(KCurrencyCalculator); auto price = d->ui->m_conversionRate->value(); if (price.isNegative()) { d->ui->m_conversionRate->setValue(-price); slotUpdateRate(QString()); return; } if (!price.isZero()) { d->ui->m_conversionRate->loadText(price.formatMoney(QString(), d->m_fromCurrency.pricePrecision())); d->m_result = (d->m_value * price).convert(d->m_resultFraction); d->ui->m_toAmount->loadText(d->m_result.formatMoney(QString(), MyMoneyMoney::denomToPrec(d->m_resultFraction))); } d->updateExample(price); } void KCurrencyCalculator::accept() { Q_D(KCurrencyCalculator); if (d->ui->m_conversionRate->isEnabled()) slotUpdateRate(QString()); else slotUpdateResult(QString()); if (d->ui->m_updateButton->isChecked()) { auto pr = MyMoneyFile::instance()->price(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date()); if (!pr.isValid() || pr.date() != d->ui->m_dateEdit->date() || (pr.date() == d->ui->m_dateEdit->date() && pr.rate(d->m_fromCurrency.id()) != price())) { pr = MyMoneyPrice(d->m_fromCurrency.id(), d->m_toCurrency.id(), d->ui->m_dateEdit->date(), price(), i18n("User")); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addPrice(pr); ft.commit(); } catch (const MyMoneyException &) { qDebug("Cannot add price"); } } } // remember setting for next round KMyMoneyGlobalSettings::setPriceHistoryUpdate(d->ui->m_updateButton->isChecked()); QDialog::accept(); } MyMoneyMoney KCurrencyCalculator::price() const { Q_D(const KCurrencyCalculator); // This should fix https://bugs.kde.org/show_bug.cgi?id=205254 and // https://bugs.kde.org/show_bug.cgi?id=325953 as well as // https://bugs.kde.org/show_bug.cgi?id=300965 if (d->ui->m_amountButton->isChecked()) return d->ui->m_toAmount->value().abs() / d->m_value.abs(); else return d->ui->m_conversionRate->value(); } diff --git a/kmymoney/dialogs/kcurrencyeditdlg.cpp b/kmymoney/dialogs/kcurrencyeditdlg.cpp index 84cd61ba3..43fa897ed 100644 --- a/kmymoney/dialogs/kcurrencyeditdlg.cpp +++ b/kmymoney/dialogs/kcurrencyeditdlg.cpp @@ -1,448 +1,448 @@ /*************************************************************************** kcurrencyeditdlg.cpp - description ------------------- begin : Wed Mar 24 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Alvaro Soliverez (C) 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. * * * ***************************************************************************/ #include "kcurrencyeditdlg.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kcurrencyeditdlg.h" #include "ui_kcurrencyeditordlg.h" #include "ui_kavailablecurrencydlg.h" #include "mymoneyexception.h" #include "mymoneysecurity.h" #include "mymoneyfile.h" #include "mymoneyprice.h" #include "kavailablecurrencydlg.h" #include "kcurrencyeditordlg.h" #include "kmymoneyutils.h" #include "icons/icons.h" #include "storageenums.h" using namespace Icons; // this delegate is needed to disable editing the currency id (column 1) // since QTreeWidgetItem has only one set of flags for the whole row // the column editable property couldn't be set in an easier way class KCurrencyEditDelegate : public QStyledItemDelegate { public: explicit KCurrencyEditDelegate(QObject *parent = 0); protected: QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; }; KCurrencyEditDelegate::KCurrencyEditDelegate(QObject* parent): QStyledItemDelegate(parent) { } QWidget *KCurrencyEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.column() == 1) return 0; return QStyledItemDelegate::createEditor(parent, option, index); } class KCurrencyEditDlgPrivate { Q_DISABLE_COPY(KCurrencyEditDlgPrivate) Q_DECLARE_PUBLIC(KCurrencyEditDlg) public: - KCurrencyEditDlgPrivate(KCurrencyEditDlg *qq) : + explicit KCurrencyEditDlgPrivate(KCurrencyEditDlg *qq) : q_ptr(qq), ui(new Ui::KCurrencyEditDlg) { } ~KCurrencyEditDlgPrivate() { delete ui; } enum removalModeE :int { RemoveSelected, RemoveUnused }; void removeCurrency(const removalModeE& mode) { Q_Q(KCurrencyEditDlg); auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; QBitArray skip((int)eStorage::Reference::Count); skip.fill(false); // check reference to all... skip.setBit((int)eStorage::Reference::Price); // ...except price QTreeWidgetItemIterator it (ui->m_currencyList); // iterate over whole tree if (mode == RemoveUnused) { while (*it) { MyMoneySecurity currency = (*it)->data(0, Qt::UserRole).value(); if (file->baseCurrency() != currency && !file->isReferenced(currency, skip)) KMyMoneyUtils::deleteSecurity(currency, q); ++it; } } else if (mode == RemoveSelected) { QList currencyRows = ui->m_currencyList->selectedItems(); foreach(auto currencyRow, currencyRows) { MyMoneySecurity currency = currencyRow->data(0, Qt::UserRole).value(); if (file->baseCurrency() != currency && !file->isReferenced(currency, skip)) KMyMoneyUtils::deleteSecurity(currency, q); } } ft.commit(); ui->m_removeUnusedCurrencyButton->setDisabled(file->currencyList().count() <= 1); } KCurrencyEditDlg *q_ptr; Ui::KCurrencyEditDlg *ui; KAvailableCurrencyDlg *m_availableCurrencyDlg; KCurrencyEditorDlg *m_currencyEditorDlg; MyMoneySecurity m_currency; /** * Search widget for the list */ KTreeWidgetSearchLineWidget* m_searchWidget; }; KCurrencyEditDlg::KCurrencyEditDlg(QWidget *parent) : QDialog(parent), d_ptr(new KCurrencyEditDlgPrivate(this)) { Q_D(KCurrencyEditDlg); d->ui->setupUi(this); d->m_searchWidget = new KTreeWidgetSearchLineWidget(this, d->ui->m_currencyList); d->m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); d->m_searchWidget->setFocus(); d->ui->verticalLayout->insertWidget(0, d->m_searchWidget); d->ui->m_currencyList->setItemDelegate(new KCurrencyEditDelegate(d->ui->m_currencyList)); d->ui->m_closeButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogClose])); d->ui->m_editCurrencyButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit])); d->ui->m_selectBaseCurrencyButton->setIcon(QIcon::fromTheme(g_Icons[Icon::KMyMoney])); connect(d->ui->m_currencyList, &QWidget::customContextMenuRequested, this, &KCurrencyEditDlg::slotOpenContextMenu); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KCurrencyEditDlg::slotLoadCurrencies); connect(d->ui->m_currencyList, &QTreeWidget::itemChanged, this, static_cast(&KCurrencyEditDlg::slotUpdateCurrency)); connect(d->ui->m_currencyList, &QTreeWidget::itemSelectionChanged, this, &KCurrencyEditDlg::slotItemSelectionChanged); connect(d->ui->m_selectBaseCurrencyButton, &QAbstractButton::clicked, this, &KCurrencyEditDlg::slotSelectBaseCurrency); connect(d->ui->m_addCurrencyButton, &QAbstractButton::clicked, this, &KCurrencyEditDlg::slotAddCurrency); connect(d->ui->m_removeCurrencyButton, &QAbstractButton::clicked, this, &KCurrencyEditDlg::slotRemoveCurrency); connect(d->ui->m_editCurrencyButton, &QAbstractButton::clicked, this, &KCurrencyEditDlg::slotEditCurrency); connect(d->ui->m_removeUnusedCurrencyButton, &QAbstractButton::clicked, this, &KCurrencyEditDlg::slotRemoveUnusedCurrency); QTimer::singleShot(10, this, SLOT(timerDone())); } void KCurrencyEditDlg::timerDone() { Q_D(KCurrencyEditDlg); slotLoadCurrencies(); //resize the column widths for (auto i = 0; i < 3; ++i) d->ui->m_currencyList->resizeColumnToContents(i); if (!d->m_currency.id().isEmpty()) { QTreeWidgetItemIterator it(d->ui->m_currencyList); QTreeWidgetItem* q; while ((q = *it) != 0) { if (q->text(1) == d->m_currency.id()) { d->ui->m_currencyList->scrollToItem(q); break; } ++it; } } } KCurrencyEditDlg::~KCurrencyEditDlg() { Q_D(KCurrencyEditDlg); delete d; } void KCurrencyEditDlg::slotLoadCurrencies() { Q_D(KCurrencyEditDlg); disconnect(d->ui->m_currencyList, &QTreeWidget::currentItemChanged, this, static_cast(&KCurrencyEditDlg::slotSelectCurrency)); disconnect(d->ui->m_currencyList, &QTreeWidget::itemChanged, this, static_cast(&KCurrencyEditDlg::slotUpdateCurrency)); QList list = MyMoneyFile::instance()->currencyList(); QList::ConstIterator it; QTreeWidgetItem *first = 0; QString localCurrency(localeconv()->int_curr_symbol); localCurrency.truncate(3); QString baseCurrency; try { baseCurrency = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } // construct a transparent 16x16 pixmap QPixmap empty(16, 16); QBitmap mask(16, 16); mask.clear(); empty.setMask(mask); d->ui->m_currencyList->clear(); for (it = list.constBegin(); it != list.constEnd(); ++it) { QTreeWidgetItem *p = new QTreeWidgetItem(d->ui->m_currencyList); p->setText(0, (*it).name()); p->setData(0, Qt::UserRole, QVariant::fromValue(*it)); p->setFlags(p->flags() | Qt::ItemIsEditable); p->setText(1, (*it).id()); p->setText(2, (*it).tradingSymbol()); if ((*it).id() == baseCurrency) { p->setData(0, Qt::DecorationRole, QIcon::fromTheme(g_Icons[Icon::KMyMoney])); if (d->m_currency.id().isEmpty()) first = p; } else { p->setData(0, Qt::DecorationRole, empty); } // if we had a previously selected if (!d->m_currency.id().isEmpty()) { if (d->m_currency.id() == p->text(1)) first = p; } else if ((*it).id() == localCurrency && !first) first = p; } d->ui->m_removeUnusedCurrencyButton->setDisabled(list.count() <= 1); d->ui->m_currencyList->sortItems(0, Qt::AscendingOrder); connect(d->ui->m_currencyList, &QTreeWidget::currentItemChanged, this, static_cast(&KCurrencyEditDlg::slotSelectCurrency)); connect(d->ui->m_currencyList, &QTreeWidget::itemChanged, this, static_cast(&KCurrencyEditDlg::slotUpdateCurrency)); if (first == 0) first = d->ui->m_currencyList->invisibleRootItem()->child(0); if (first != 0) { d->ui->m_currencyList->setCurrentItem(first); d->ui->m_currencyList->scrollToItem(first); } slotSelectCurrency(first); } void KCurrencyEditDlg::slotUpdateCurrency(QTreeWidgetItem* citem, int) { slotUpdateCurrency(citem, nullptr); } void KCurrencyEditDlg::slotUpdateCurrency(QTreeWidgetItem* citem, QTreeWidgetItem *pitem) { Q_D(KCurrencyEditDlg); Q_UNUSED(pitem) //if there is no current item selected, exit if (!d->ui->m_currencyList->currentItem() || citem != d->ui->m_currencyList->currentItem()) return; //verify that the stored currency id is not empty and the edited fields are not empty either if (!d->m_currency.id().isEmpty() && !d->ui->m_currencyList->currentItem()->text(2).isEmpty() && !d->ui->m_currencyList->currentItem()->text(0).isEmpty()) { //check that either the name or the id have changed if (d->ui->m_currencyList->currentItem()->text(2) != d->m_currency.tradingSymbol() || d->ui->m_currencyList->currentItem()->text(0) != d->m_currency.name()) { //update the name and the id d->m_currency.setName(d->ui->m_currencyList->currentItem()->text(0)); d->m_currency.setTradingSymbol(d->ui->m_currencyList->currentItem()->text(2)); emit updateCurrency(d->m_currency.id(), d->m_currency.name(), d->m_currency.tradingSymbol()); } } } void KCurrencyEditDlg::slotSelectCurrency(const QString& id) { Q_D(KCurrencyEditDlg); QTreeWidgetItemIterator it(d->ui->m_currencyList); while (*it) { if ((*it)->text(1) == id) { d->ui->m_currencyList->blockSignals(true); slotSelectCurrency(*it); d->ui->m_currencyList->setCurrentItem(*it); d->ui->m_currencyList->scrollToItem(*it); d->ui->m_currencyList->blockSignals(false); break; } ++it; } } void KCurrencyEditDlg::slotSelectCurrency(QTreeWidgetItem *citem, QTreeWidgetItem *pitem) { Q_UNUSED(pitem) slotSelectCurrency(citem); } void KCurrencyEditDlg::slotSelectCurrency(QTreeWidgetItem *item) { Q_D(KCurrencyEditDlg); auto file = MyMoneyFile::instance(); QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &) { } if (item) { try { d->m_currency = file->security(item->text(1)); } catch (const MyMoneyException &) { d->m_currency = MyMoneySecurity(); } QBitArray skip((int)eStorage::Reference::Count); skip.fill(false); skip.setBit((int)eStorage::Reference::Price); const bool rc1 = d->m_currency.id() == baseId; const bool rc2 = file->isReferenced(d->m_currency, skip); const int count = d->ui->m_currencyList->selectedItems().count(); d->ui->m_selectBaseCurrencyButton->setDisabled(rc1 || count != 1); d->ui->m_editCurrencyButton->setDisabled(count != 1); d->ui->m_removeCurrencyButton->setDisabled((rc1 || rc2) && count <= 1); emit selectObject(d->m_currency); } } void KCurrencyEditDlg::slotItemSelectionChanged() { Q_D(KCurrencyEditDlg); int count = d->ui->m_currencyList->selectedItems().count(); if (!d->ui->m_selectBaseCurrencyButton->isEnabled() && count == 1) slotSelectCurrency(d->ui->m_currencyList->currentItem()); if (count > 1) d->ui->m_removeCurrencyButton->setEnabled(true); } void KCurrencyEditDlg::slotStartRename() { Q_D(KCurrencyEditDlg); QTreeWidgetItemIterator it_l(d->ui->m_currencyList, QTreeWidgetItemIterator::Selected); QTreeWidgetItem* it_v; if ((it_v = *it_l) != 0) { d->ui->m_currencyList->editItem(it_v, 0); } } void KCurrencyEditDlg::slotOpenContextMenu(const QPoint& p) { Q_D(KCurrencyEditDlg); QTreeWidgetItem* item = d->ui->m_currencyList->itemAt(p); if (item) emit openContextMenu(item->data(0, Qt::UserRole).value()); } void KCurrencyEditDlg::slotSelectBaseCurrency() { Q_D(KCurrencyEditDlg); if (!d->m_currency.id().isEmpty()) { QTreeWidgetItem* p = d->ui->m_currencyList->currentItem(); emit selectBaseCurrency(d->m_currency); // in case the dataChanged() signal was not sent out (nested FileTransaction) // we update the list manually if (p == d->ui->m_currencyList->currentItem()) slotLoadCurrencies(); } } void KCurrencyEditDlg::slotAddCurrency() { Q_D(KCurrencyEditDlg); d->m_availableCurrencyDlg = new KAvailableCurrencyDlg; // create new dialog for selecting currencies to add if (d->m_availableCurrencyDlg->exec() != QDialog::Rejected) { auto file = MyMoneyFile::instance(); QMap ancientCurrencies = file->ancientCurrencies(); MyMoneyFileTransaction ft; QList currencyRows = d->m_availableCurrencyDlg->ui->m_currencyList->selectedItems(); // get selected currencies from new dialog foreach (auto currencyRow, currencyRows) { MyMoneySecurity currency = currencyRow->data(0, Qt::UserRole).value(); file->addCurrency(currency); if (ancientCurrencies.value(currency, MyMoneyPrice()) != MyMoneyPrice()) // if ancient currency is added... file->addPrice(ancientCurrencies[currency]); // ...we want to add last known exchange rate as well } ft.commit(); d->ui->m_removeUnusedCurrencyButton->setDisabled(file->currencyList().count() <= 1); } delete d->m_availableCurrencyDlg; } void KCurrencyEditDlg::slotRemoveCurrency() { Q_D(KCurrencyEditDlg); d->removeCurrency(KCurrencyEditDlgPrivate::RemoveSelected); } void KCurrencyEditDlg::slotRemoveUnusedCurrency() { Q_D(KCurrencyEditDlg); d->removeCurrency(KCurrencyEditDlgPrivate::RemoveUnused); } void KCurrencyEditDlg::slotEditCurrency() { Q_D(KCurrencyEditDlg); MyMoneySecurity currency = d->ui->m_currencyList->currentItem()->data(0, Qt::UserRole).value(); d->m_currencyEditorDlg = new KCurrencyEditorDlg(currency); // create new dialog for editing currency if (d->m_currencyEditorDlg->exec() != QDialog::Rejected) { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; currency.setPricePrecision(d->m_currencyEditorDlg->ui->m_pricePrecision->value()); try { file->modifyCurrency(currency); ft.commit(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } } delete d->m_currencyEditorDlg; } diff --git a/kmymoney/dialogs/kcurrencyeditordlg.h b/kmymoney/dialogs/kcurrencyeditordlg.h index 4b3ce838d..85de420e2 100644 --- a/kmymoney/dialogs/kcurrencyeditordlg.h +++ b/kmymoney/dialogs/kcurrencyeditordlg.h @@ -1,51 +1,51 @@ /*************************************************************************** kcurrencyeditordlg.h - description ------------------- begin : Sat Apr 09 2017 copyright : (C) 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. * * * ***************************************************************************/ #ifndef KCURRENCYEDITORDLG_H #define KCURRENCYEDITORDLG_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes namespace Ui { class KCurrencyEditorDlg; } class MyMoneySecurity; class KCurrencyEditorDlg : public QDialog { Q_OBJECT public: - KCurrencyEditorDlg(MyMoneySecurity ¤cy, QWidget *parent = nullptr); + explicit KCurrencyEditorDlg(MyMoneySecurity ¤cy, QWidget *parent = nullptr); ~KCurrencyEditorDlg(); Ui::KCurrencyEditorDlg* ui; protected slots: void loadCurrency(MyMoneySecurity& currency); }; #endif diff --git a/kmymoney/dialogs/keditscheduledlg.cpp b/kmymoney/dialogs/keditscheduledlg.cpp index 157307c60..0277aea74 100644 --- a/kmymoney/dialogs/keditscheduledlg.cpp +++ b/kmymoney/dialogs/keditscheduledlg.cpp @@ -1,664 +1,664 @@ /*************************************************************************** keditscheduledlg.cpp - description ------------------- begin : Mon Sep 3 2007 copyright : (C) 2007 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #include "keditscheduledlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_keditscheduledlg.h" #include "tabbar.h" #include "mymoneyfile.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "register.h" #include "transactionform.h" #include "transaction.h" #include "transactioneditor.h" #include "kmymoneylineedit.h" #include "kmymoneydateinput.h" #include "kmymoneymvccombo.h" #include "kguiutils.h" #include "kmymoney.h" #include "mymoneyenums.h" #include "widgetenums.h" using namespace eMyMoney; class KEditScheduleDlgPrivate { Q_DISABLE_COPY(KEditScheduleDlgPrivate) Q_DECLARE_PUBLIC(KEditScheduleDlg) public: - KEditScheduleDlgPrivate(KEditScheduleDlg *qq) : + explicit KEditScheduleDlgPrivate(KEditScheduleDlg *qq) : q_ptr(qq), ui(new Ui::KEditScheduleDlg) { } ~KEditScheduleDlgPrivate() { delete ui; } void init() { Q_Q(KEditScheduleDlg); ui->setupUi(q); m_requiredFields = new KMandatoryFieldGroup(q); m_requiredFields->setOkButton(ui->buttonBox->button(QDialogButtonBox::Ok)); // button to be enabled when all fields present // make sure, we have a tabbar with the form // insert it after the horizontal line ui->m_paymentInformationLayout->insertWidget(2, ui->m_form->getTabBar(ui->m_form->parentWidget())); // we never need to see the register ui->m_register->hide(); // ... setup the form ... ui->m_form->setupForm(m_schedule.account()); // ... and the register ... ui->m_register->clear(); // ... now add the transaction to register and form ... auto t = transaction(); if (m_schedule.transaction().splits().isEmpty()) m_item = KMyMoneyRegister::Register::transactionFactory(ui->m_register, t, MyMoneySplit(), 0); else m_item = KMyMoneyRegister::Register::transactionFactory(ui->m_register, t, m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : m_schedule.transaction().splits().front(), 0); ui->m_register->selectItem(m_item); // show the account row m_item->setShowRowInForm(0, true); ui->m_form->slotSetTransaction(m_item); // setup widget contents ui->m_nameEdit->setText(m_schedule.name()); ui->m_frequencyEdit->setCurrentItem((int)m_schedule.occurrencePeriod()); if (ui->m_frequencyEdit->currentItem() == Schedule::Occurrence::Any) ui->m_frequencyEdit->setCurrentItem((int)Schedule::Occurrence::Monthly); q->slotFrequencyChanged((int)ui->m_frequencyEdit->currentItem()); ui->m_frequencyNoEdit->setValue(m_schedule.occurrenceMultiplier()); // load option widgets ui->m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit); ui->m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit); ui->m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit); ui->m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder); ui->m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer); ui->m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque); ui->m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other); auto method = m_schedule.paymentType(); if (method == Schedule::PaymentType::Any) method = Schedule::PaymentType::Other; ui->m_paymentMethodEdit->setCurrentItem((int)method); switch (m_schedule.weekendOption()) { case Schedule::WeekendOption::MoveNothing: ui->m_weekendOptionEdit->setCurrentIndex(0); break; case Schedule::WeekendOption::MoveBefore: ui->m_weekendOptionEdit->setCurrentIndex(1); break; case Schedule::WeekendOption::MoveAfter: ui->m_weekendOptionEdit->setCurrentIndex(2); break; } ui->m_estimateEdit->setChecked(!m_schedule.isFixed()); ui->m_lastDayInMonthEdit->setChecked(m_schedule.lastDayInMonth()); ui->m_autoEnterEdit->setChecked(m_schedule.autoEnter()); ui->m_endSeriesEdit->setChecked(m_schedule.willEnd()); ui->m_endOptionsFrame->setEnabled(m_schedule.willEnd()); if (m_schedule.willEnd()) { ui->m_RemainingEdit->setValue(m_schedule.transactionsRemaining()); ui->m_FinalPaymentEdit->setDate(m_schedule.endDate()); } q->connect(ui->m_RemainingEdit, static_cast(&QSpinBox::valueChanged), q, &KEditScheduleDlg::slotRemainingChanged); q->connect(ui->m_FinalPaymentEdit, &KMyMoneyDateInput::dateChanged, q, &KEditScheduleDlg::slotEndDateChanged); q->connect(ui->m_frequencyEdit, &KMyMoneyGeneralCombo::itemSelected, q, &KEditScheduleDlg::slotFrequencyChanged); q->connect(ui->m_frequencyNoEdit, static_cast(&QSpinBox::valueChanged), q, &KEditScheduleDlg::slotOccurrenceMultiplierChanged); q->connect(ui->buttonBox, &QDialogButtonBox::helpRequested, q, &KEditScheduleDlg::slotShowHelp); q->setModal(true); // force the initial height to be as small as possible QTimer::singleShot(0, q, SLOT(slotSetupSize())); // we just hide the variation field for now and enable the logic // once we have a respective member in the MyMoneySchedule object ui->m_variation->hide(); } /** * Helper method to recalculate and update Transactions Remaining * when other values are changed */ void updateTransactionsRemaining() { auto remain = m_schedule.transactionsRemaining(); if (remain != ui->m_RemainingEdit->value()) { ui->m_RemainingEdit->blockSignals(true); ui->m_RemainingEdit->setValue(remain); ui->m_RemainingEdit->blockSignals(false); } } MyMoneyTransaction transaction() const { auto t = m_schedule.transaction(); if (m_editor) { m_editor->createTransaction(t, m_schedule.transaction(), m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : m_schedule.transaction().splits().front(), false); } t.clearId(); t.setEntryDate(QDate()); return t; } KEditScheduleDlg *q_ptr; Ui::KEditScheduleDlg *ui; MyMoneySchedule m_schedule; KMyMoneyRegister::Transaction* m_item; QWidgetList m_tabOrderWidgets; TransactionEditor* m_editor; KMandatoryFieldGroup* m_requiredFields; }; KEditScheduleDlg::KEditScheduleDlg(const MyMoneySchedule& schedule, QWidget *parent) : QDialog(parent), d_ptr(new KEditScheduleDlgPrivate(this)) { Q_D(KEditScheduleDlg); d->m_schedule = schedule; d->m_editor = 0; d->init(); } KEditScheduleDlg::~KEditScheduleDlg() { Q_D(KEditScheduleDlg); delete d; } void KEditScheduleDlg::slotSetupSize() { resize(width(), minimumSizeHint().height()); } TransactionEditor* KEditScheduleDlg::startEdit() { Q_D(KEditScheduleDlg); KMyMoneyRegister::SelectedTransactions list(d->ui->m_register); TransactionEditor* editor = d->m_item->createEditor(d->ui->m_form, list, QDate()); // check that we use the same transaction commodity in all selected transactions // if not, we need to update this in the editor's list. The user can also bail out // of this operation which means that we have to stop editing here. if (editor && !d->m_schedule.account().id().isEmpty()) { if (!editor->fixTransactionCommodity(d->m_schedule.account())) { // if the user wants to quit, we need to destroy the editor // and bail out delete editor; editor = 0; } } if (editor) { editor->setScheduleInfo(d->ui->m_nameEdit->text()); connect(editor, &TransactionEditor::transactionDataSufficient, d->ui->buttonBox->button(QDialogButtonBox::Ok), &QWidget::setEnabled); connect(editor, &TransactionEditor::escapePressed, d->ui->buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::animateClick); connect(editor, &TransactionEditor::returnPressed, d->ui->buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::animateClick); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); // connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions))); connect(editor, &TransactionEditor::createPayee, kmymoney, static_cast(&KMyMoneyApp::slotPayeeNew)); connect(editor, &TransactionEditor::createTag, kmymoney, static_cast(&KMyMoneyApp::slotTagNew)); connect(editor, &TransactionEditor::createCategory, kmymoney, static_cast(&KMyMoneyApp::slotCategoryNew)); connect(editor, &TransactionEditor::createSecurity, kmymoney, static_cast(&KMyMoneyApp::slotInvestmentNew)); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); // create the widgets, place them in the parent and load them with data // setup tab order d->m_tabOrderWidgets.clear(); eWidgets::eRegister::Action action = eWidgets::eRegister::Action::Withdrawal; switch (d->m_schedule.type()) { case Schedule::Type::Deposit: action = eWidgets::eRegister::Action::Deposit; break; case Schedule::Type::Bill: action = eWidgets::eRegister::Action::Withdrawal; editor->setPaymentMethod(d->m_schedule.paymentType()); break; case Schedule::Type::Transfer: action = eWidgets::eRegister::Action::Transfer; break; default: // if we end up here, we don't have a known schedule type (yet). in this case, we just glimpse // into the transaction and determine the type. in case we don't have a transaction with splits // we stick with the default action already set up if (d->m_schedule.transaction().splits().count() > 0) { auto isDeposit = false; auto isTransfer = false; auto splits = d->m_schedule.transaction().splits(); foreach (const auto split, splits) { if (split.accountId() == d->m_schedule.account().id()) { isDeposit = !(split.shares().isNegative()); } else { auto acc = MyMoneyFile::instance()->account(split.accountId()); if (acc.isAssetLiability() && d->m_schedule.transaction().splits().count() == 2) { isTransfer = true; } } } if (isTransfer) action = eWidgets::eRegister::Action::Transfer; else if (isDeposit) action = eWidgets::eRegister::Action::Deposit; } break; } editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action); // if it's not a check, then we need to clear // a possibly assigned check number if (d->m_schedule.paymentType() != Schedule::PaymentType::WriteChecque) { QWidget* w = editor->haveWidget("number"); if (w) dynamic_cast(w)->loadText(QString()); } Q_ASSERT(!d->m_tabOrderWidgets.isEmpty()); d->m_tabOrderWidgets.push_front(d->ui->m_paymentMethodEdit); // editor->setup() leaves the tabbar as the last widget in the stack, but we // need it as first here. So we move it around. QWidget* w = editor->haveWidget("tabbar"); if (w) { int idx = d->m_tabOrderWidgets.indexOf(w); if (idx != -1) { d->m_tabOrderWidgets.removeAt(idx); d->m_tabOrderWidgets.push_front(w); } } // don't forget our three buttons and additional widgets // make sure to use the correct order d->m_tabOrderWidgets.push_front(d->ui->m_frequencyEdit); d->m_tabOrderWidgets.push_front(d->ui->m_frequencyNoEdit); d->m_tabOrderWidgets.push_front(d->ui->m_nameEdit); d->m_tabOrderWidgets.append(d->ui->m_weekendOptionEdit); d->m_tabOrderWidgets.append(d->ui->m_estimateEdit); d->m_tabOrderWidgets.append(d->ui->m_variation); d->m_tabOrderWidgets.append(d->ui->m_lastDayInMonthEdit); d->m_tabOrderWidgets.append(d->ui->m_autoEnterEdit); d->m_tabOrderWidgets.append(d->ui->m_endSeriesEdit); d->m_tabOrderWidgets.append(d->ui->m_RemainingEdit); d->m_tabOrderWidgets.append(d->ui->m_FinalPaymentEdit); d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Ok)); d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Cancel)); d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Help)); for (auto i = 0; i < d->m_tabOrderWidgets.size(); ++i) { QWidget* w = d->m_tabOrderWidgets.at(i); if (w) { w->installEventFilter(this); w->installEventFilter(editor); } } // connect the postdate modification signal to our update routine KMyMoneyDateInput* dateEdit = dynamic_cast(editor->haveWidget("postdate")); if (dateEdit) connect(dateEdit, &KMyMoneyDateInput::dateChanged, this, &KEditScheduleDlg::slotPostDateChanged); d->ui->m_nameEdit->setFocus(); // add the required fields to the mandatory group d->m_requiredFields->add(d->ui->m_nameEdit); d->m_requiredFields->add(editor->haveWidget("account")); d->m_requiredFields->add(editor->haveWidget("category")); d->m_requiredFields->add(editor->haveWidget("amount")); // fix labels QLabel* label = dynamic_cast(editor->haveWidget("date-label")); if (label) { label->setText(i18n("Next due date")); } d->m_editor = editor; slotSetPaymentMethod((int)d->m_schedule.paymentType()); connect(d->ui->m_paymentMethodEdit, &KMyMoneyGeneralCombo::itemSelected, this, &KEditScheduleDlg::slotSetPaymentMethod); connect(editor, &TransactionEditor::operationTypeChanged, this, &KEditScheduleDlg::slotFilterPaymentType); } return editor; } void KEditScheduleDlg::accept() { Q_D(KEditScheduleDlg); // Force the focus to be on the OK button. This will trigger creation // of any unknown objects (payees, categories etc.) d->ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); // only accept if the button is really still enabled. We could end // up here, if the user filled all fields, the focus is on the category // field, but the category is not yet existent. When the user presses the // OK button in this context, he will be asked if he wants to create // the category or not. In case he decides no, we end up here with no // category filled in, so we don't run through the final acceptance. if (d->ui->buttonBox->button(QDialogButtonBox::Ok)->isEnabled()) QDialog::accept(); } const MyMoneySchedule& KEditScheduleDlg::schedule() { Q_D(KEditScheduleDlg); if (d->m_editor) { auto t = d->transaction(); if (d->m_schedule.nextDueDate() != t.postDate()) { d->m_schedule.setNextDueDate(t.postDate()); d->m_schedule.setStartDate(t.postDate()); } d->m_schedule.setTransaction(t); d->m_schedule.setName(d->ui->m_nameEdit->text()); d->m_schedule.setFixed(!d->ui->m_estimateEdit->isChecked()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); switch (d->ui->m_weekendOptionEdit->currentIndex()) { case 0: d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveNothing); break; case 1: d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveBefore); break; case 2: d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveAfter); break; } d->m_schedule.setType(Schedule::Type::Bill); KMyMoneyTransactionForm::TabBar* tabbar = dynamic_cast(d->m_editor->haveWidget("tabbar")); if (tabbar) { switch (static_cast(tabbar->currentIndex())) { case eWidgets::eRegister::Action::Deposit: d->m_schedule.setType(Schedule::Type::Deposit); break; default: case eWidgets::eRegister::Action::Withdrawal: d->m_schedule.setType(Schedule::Type::Bill); break; case eWidgets::eRegister::Action::Transfer: d->m_schedule.setType(Schedule::Type::Transfer); break; } } else { qDebug("No tabbar found in KEditScheduleDlg::schedule(). Defaulting type to BILL"); } if(d->ui->m_lastDayInMonthEdit->isEnabled()) d->m_schedule.setLastDayInMonth(d->ui->m_lastDayInMonthEdit->isChecked()); else d->m_schedule.setLastDayInMonth(false); d->m_schedule.setAutoEnter(d->ui->m_autoEnterEdit->isChecked()); d->m_schedule.setPaymentType(static_cast(d->ui->m_paymentMethodEdit->currentItem())); if (d->ui->m_endSeriesEdit->isEnabled() && d->ui->m_endSeriesEdit->isChecked()) { d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); } else { d->m_schedule.setEndDate(QDate()); } } return d->m_schedule; } bool KEditScheduleDlg::focusNextPrevChild(bool next) { Q_D(KEditScheduleDlg); auto rc = false; auto w = qApp->focusWidget(); auto currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", qPrintable(w->objectName())); w = w->parentWidget(); currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { do { // if(w) qDebug("tab order is at '%s (%d/%d)'", qPrintable(w->objectName()), currentWidgetIndex, d->m_tabOrderWidgets.size()); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = d->m_tabOrderWidgets[currentWidgetIndex]; // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w); if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' as focus", qPrintable(w->objectName())); w->setFocus(); rc = true; } } while (rc == false); } return rc; } void KEditScheduleDlg::resizeEvent(QResizeEvent* ev) { Q_D(KEditScheduleDlg); d->ui->m_register->resize((int)eWidgets::eTransaction::Column::Detail); d->ui->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1); QDialog::resizeEvent(ev); } void KEditScheduleDlg::slotRemainingChanged(int value) { Q_D(KEditScheduleDlg); // Make sure the required fields are set auto dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate")); d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); if (d->m_schedule.transactionsRemaining() != value) { d->ui->m_FinalPaymentEdit->blockSignals(true); d->ui->m_FinalPaymentEdit->setDate(d->m_schedule.dateAfter(value)); d->ui->m_FinalPaymentEdit->blockSignals(false); } } void KEditScheduleDlg::slotEndDateChanged(const QDate& date) { Q_D(KEditScheduleDlg); // Make sure the required fields are set auto dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate")); d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); if (d->m_schedule.endDate() != date) { d->m_schedule.setEndDate(date); d->updateTransactionsRemaining(); } } void KEditScheduleDlg::slotPostDateChanged(const QDate& date) { Q_D(KEditScheduleDlg); if (d->m_schedule.nextDueDate() != date) { if (d->ui->m_endOptionsFrame->isEnabled()) { d->m_schedule.setNextDueDate(date); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); d->updateTransactionsRemaining(); } } } void KEditScheduleDlg::slotSetPaymentMethod(int item) { Q_D(KEditScheduleDlg); auto dateEdit = dynamic_cast(d->m_editor->haveWidget("number")); if (dateEdit) { dateEdit->setVisible(item == (int)Schedule::PaymentType::WriteChecque); // hiding the label does not work, because the label underneath will shine // through. So we either write the label or a blank QLabel* label = dynamic_cast(d->m_editor->haveWidget("number-label")); if (label) { label->setText((item == (int)Schedule::PaymentType::WriteChecque) ? i18n("Number") : " "); } } } void KEditScheduleDlg::slotFrequencyChanged(int item) { Q_D(KEditScheduleDlg); d->ui->m_endSeriesEdit->setEnabled(item != (int)Schedule::Occurrence::Once); bool isEndSeries = d->ui->m_endSeriesEdit->isChecked(); if (isEndSeries) d->ui->m_endOptionsFrame->setEnabled(item != (int)Schedule::Occurrence::Once); switch (item) { case (int)Schedule::Occurrence::Daily: case (int)Schedule::Occurrence::Weekly: d->ui->m_frequencyNoEdit->setEnabled(true); d->ui->m_lastDayInMonthEdit->setEnabled(false); break; case (int)Schedule::Occurrence::EveryHalfMonth: case (int)Schedule::Occurrence::Monthly: case (int)Schedule::Occurrence::Yearly: // Supports Frequency Number d->ui->m_frequencyNoEdit->setEnabled(true); d->ui->m_lastDayInMonthEdit->setEnabled(true); break; default: // Multiplier is always 1 d->ui->m_frequencyNoEdit->setEnabled(false); d->ui->m_frequencyNoEdit->setValue(1); d->ui->m_lastDayInMonthEdit->setEnabled(true); break; } if (isEndSeries && (item != (int)Schedule::Occurrence::Once)) { // Changing the frequency changes the number // of remaining transactions KMyMoneyDateInput* dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate")); d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); d->m_schedule.setOccurrencePeriod(static_cast(item)); d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); d->updateTransactionsRemaining(); } } void KEditScheduleDlg::slotOccurrenceMultiplierChanged(int multiplier) { Q_D(KEditScheduleDlg); // Make sure the required fields are set auto oldOccurrenceMultiplier = d->m_schedule.occurrenceMultiplier(); if (multiplier != oldOccurrenceMultiplier) { if (d->ui->m_endOptionsFrame->isEnabled()) { KMyMoneyDateInput* dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate")); d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrenceMultiplier(multiplier); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); d->updateTransactionsRemaining(); } } } void KEditScheduleDlg::slotShowHelp() { KHelpClient::invokeHelp("details.schedules.intro"); } void KEditScheduleDlg::slotFilterPaymentType(int index) { Q_D(KEditScheduleDlg); //save selected item to reload if possible auto selectedId = d->ui->m_paymentMethodEdit->itemData(d->ui->m_paymentMethodEdit->currentIndex(), Qt::UserRole).toInt(); //clear and reload the widget with the correct items d->ui->m_paymentMethodEdit->clear(); // load option widgets eWidgets::eRegister::Action action = static_cast(index); if (action != eWidgets::eRegister::Action::Withdrawal) { d->ui->m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit); d->ui->m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit); } if (action != eWidgets::eRegister::Action::Deposit) { d->ui->m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit); d->ui->m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque); } d->ui->m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder); d->ui->m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer); d->ui->m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other); auto newIndex = d->ui->m_paymentMethodEdit->findData(QVariant(selectedId), Qt::UserRole, Qt::MatchExactly); if (newIndex > -1) { d->ui->m_paymentMethodEdit->setCurrentIndex(newIndex); } else { d->ui->m_paymentMethodEdit->setCurrentIndex(0); } } diff --git a/kmymoney/dialogs/kequitypriceupdatedlg.cpp b/kmymoney/dialogs/kequitypriceupdatedlg.cpp index 3db7d5464..45a123b83 100644 --- a/kmymoney/dialogs/kequitypriceupdatedlg.cpp +++ b/kmymoney/dialogs/kequitypriceupdatedlg.cpp @@ -1,805 +1,805 @@ /*************************************************************************** kequitypriceupdatedlg.cpp - description ------------------- begin : Mon Sep 1 2003 copyright : (C) 2000-2003 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jones (C) 2017 by Ł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. * * * ***************************************************************************/ #include "kequitypriceupdatedlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kequitypriceupdatedlg.h" #include "kmymoney.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "webpricequote.h" #include "kequitypriceupdateconfdlg.h" #include "dialogenums.h" #define WEBID_COL 0 #define NAME_COL 1 #define PRICE_COL 2 #define DATE_COL 3 #define KMMID_COL 4 #define SOURCE_COL 5 class KEquityPriceUpdateDlgPrivate { Q_DISABLE_COPY(KEquityPriceUpdateDlgPrivate) Q_DECLARE_PUBLIC(KEquityPriceUpdateDlg) public: - KEquityPriceUpdateDlgPrivate(KEquityPriceUpdateDlg *qq) : + explicit KEquityPriceUpdateDlgPrivate(KEquityPriceUpdateDlg *qq) : q_ptr(qq), ui(new Ui::KEquityPriceUpdateDlg) { } ~KEquityPriceUpdateDlgPrivate() { delete ui; } void init(const QString& securityId) { Q_Q(KEquityPriceUpdateDlg); ui->setupUi(q); m_fUpdateAll = false; QStringList headerList; headerList << i18n("ID") << i18nc("Equity name", "Name") << i18n("Price") << i18n("Date"); ui->lvEquityList->header()->setSortIndicator(0, Qt::AscendingOrder); ui->lvEquityList->setColumnWidth(NAME_COL, 125); // This is a "get it up and running" hack. Will replace this in the future. headerList << i18nc("Internal identifier", "Internal ID") << i18nc("Online quote source", "Source"); ui->lvEquityList->setColumnWidth(KMMID_COL, 0); ui->lvEquityList->setHeaderLabels(headerList); ui->lvEquityList->setSelectionMode(QAbstractItemView::MultiSelection); ui->lvEquityList->setAllColumnsShowFocus(true); ui->btnUpdateAll->setEnabled(false); auto file = MyMoneyFile::instance(); // // Add each price pair that we know about // // send in securityId == "XXX YYY" to get a single-shot update for XXX to YYY. // for consistency reasons, this accepts the same delimiters as WebPriceQuote::launch() QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); MyMoneySecurityPair currencyIds; if (splitrx.indexIn(securityId) != -1) { currencyIds = MyMoneySecurityPair(splitrx.cap(1), splitrx.cap(2)); } MyMoneyPriceList prices = file->priceList(); for (MyMoneyPriceList::ConstIterator it_price = prices.constBegin(); it_price != prices.constEnd(); ++it_price) { const MyMoneySecurityPair& pair = it_price.key(); if (file->security(pair.first).isCurrency() && (securityId.isEmpty() || (pair == currencyIds))) { const MyMoneyPriceEntries& entries = (*it_price); if (entries.count() > 0 && entries.begin().key() <= QDate::currentDate()) { addPricePair(pair, false); ui->btnUpdateAll->setEnabled(true); } } } // // Add each investment // QList securities = file->securityList(); for (QList::const_iterator it = securities.constBegin(); it != securities.constEnd(); ++it) { if (!(*it).isCurrency() && (securityId.isEmpty() || ((*it).id() == securityId)) && !(*it).value("kmm-online-source").isEmpty() ) { addInvestment(*it); ui->btnUpdateAll->setEnabled(true); } } // if list is empty, add the request price pair if (ui->lvEquityList->invisibleRootItem()->childCount() == 0) { addPricePair(currencyIds, true); } q->connect(ui->btnUpdateSelected, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateSelectedClicked); q->connect(ui->btnUpdateAll, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateAllClicked); q->connect(ui->m_fromDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged); q->connect(ui->m_toDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged); q->connect(&m_webQuote, &WebPriceQuote::csvquote, q, &KEquityPriceUpdateDlg::slotReceivedCSVQuote); q->connect(&m_webQuote, &WebPriceQuote::quote, q, &KEquityPriceUpdateDlg::slotReceivedQuote); q->connect(&m_webQuote, &WebPriceQuote::failed, q, &KEquityPriceUpdateDlg::slotQuoteFailed); q->connect(&m_webQuote, &WebPriceQuote::status, q, &KEquityPriceUpdateDlg::logStatusMessage); q->connect(&m_webQuote, &WebPriceQuote::error, q, &KEquityPriceUpdateDlg::logErrorMessage); q->connect(ui->lvEquityList, &QTreeWidget::itemSelectionChanged, q, &KEquityPriceUpdateDlg::slotUpdateSelection); q->connect(ui->btnConfigure, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotConfigureClicked); if (!securityId.isEmpty()) { ui->btnUpdateSelected->hide(); ui->btnUpdateAll->hide(); // delete layout1; QTimer::singleShot(100, q, SLOT(slotUpdateAllClicked())); } // Hide OK button until we have received the first update ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); q->slotUpdateSelection(); // previous versions of this dialog allowed to store a "Don't ask again" switch. // Since we don't support it anymore, we just get rid of it KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Notification Messages"); grp.deleteEntry("KEquityPriceUpdateDlg::slotQuoteFailed::Price Update Failed"); grp.sync(); grp = config->group("Equity Price Update"); int policyValue = grp.readEntry("PriceUpdatingPolicy", (int)eDialogs::UpdatePrice::Missing); if (policyValue > (int)eDialogs::UpdatePrice::Ask || policyValue < (int)eDialogs::UpdatePrice::All) m_updatingPricePolicy = eDialogs::UpdatePrice::Missing; else m_updatingPricePolicy = static_cast(policyValue); } void addPricePair(const MyMoneySecurityPair& pair, bool dontCheckExistance) { auto file = MyMoneyFile::instance(); const auto symbol = QString::fromLatin1("%1 > %2").arg(pair.first, pair.second); const auto id = QString::fromLatin1("%1 %2").arg(pair.first, pair.second); // Check that the pair does not already exist if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) { const MyMoneyPrice &pr = file->price(pair.first, pair.second); if (pr.source() != QLatin1String("KMyMoney")) { bool keep = true; if ((pair.first == file->baseCurrency().id()) || (pair.second == file->baseCurrency().id())) { const QString& foreignCurrency = file->foreignCurrency(pair.first, pair.second); // check that the foreign currency is still in use QList::const_iterator it_a; QList list; file->accountList(list); for (it_a = list.constBegin(); !dontCheckExistance && it_a != list.constEnd(); ++it_a) { // if it's an account denominated in the foreign currency // keep it if (((*it_a).currencyId() == foreignCurrency) && !(*it_a).isClosed()) break; // if it's an investment traded in the foreign currency // keep it if ((*it_a).isInvest() && !(*it_a).isClosed()) { MyMoneySecurity sec = file->security((*it_a).currencyId()); if (sec.tradingCurrency() == foreignCurrency) break; } } // if it is in use, it_a is not equal to list.end() if (it_a == list.constEnd() && !dontCheckExistance) keep = false; } if (keep) { auto item = new QTreeWidgetItem(); item->setText(WEBID_COL, symbol); item->setText(NAME_COL, i18n("%1 units in %2", pair.first, pair.second)); if (pr.isValid()) { MyMoneySecurity fromCurrency = file->currency(pair.second); MyMoneySecurity toCurrency = file->currency(pair.first); item->setText(PRICE_COL, pr.rate(pair.second).formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision())); item->setText(DATE_COL, pr.date().toString(Qt::ISODate)); } item->setText(KMMID_COL, id); item->setText(SOURCE_COL, "KMyMoney Currency"); // This string value should not be localized ui->lvEquityList->invisibleRootItem()->addChild(item); } } } } void addInvestment(const MyMoneySecurity& inv) { const auto id = inv.id(); // Check that the pair does not already exist if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) { auto file = MyMoneyFile::instance(); // check that the security is still in use QList::const_iterator it_a; QList list; file->accountList(list); for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { if ((*it_a).isInvest() && ((*it_a).currencyId() == inv.id()) && !(*it_a).isClosed()) break; } // if it is in use, it_a is not equal to list.end() if (it_a != list.constEnd()) { QString webID; WebPriceQuoteSource onlineSource(inv.value("kmm-online-source")); if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::IdentificationNumber) webID = inv.value("kmm-security-id"); // insert ISIN number... else if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::Name) webID = inv.name(); // ...or name... else webID = inv.tradingSymbol(); // ...or symbol QTreeWidgetItem* item = new QTreeWidgetItem(); item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NormalText)); if (webID.isEmpty()) { webID = i18n("[No identifier]"); item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText)); } item->setText(WEBID_COL, webID); item->setText(NAME_COL, inv.name()); MyMoneySecurity currency = file->currency(inv.tradingCurrency()); const MyMoneyPrice &pr = file->price(id.toUtf8(), inv.tradingCurrency()); if (pr.isValid()) { item->setText(PRICE_COL, pr.rate(currency.id()).formatMoney(currency.tradingSymbol(), inv.pricePrecision())); item->setText(DATE_COL, pr.date().toString(Qt::ISODate)); } item->setText(KMMID_COL, id); if (inv.value("kmm-online-quote-system") == "Finance::Quote") item->setText(SOURCE_COL, QString("Finance::Quote %1").arg(inv.value("kmm-online-source"))); else item->setText(SOURCE_COL, inv.value("kmm-online-source")); ui->lvEquityList->invisibleRootItem()->addChild(item); // If this investment is denominated in a foreign currency, ensure that // the appropriate price pair is also on the list if (currency.id() != file->baseCurrency().id()) { addPricePair(MyMoneySecurityPair(currency.id(), file->baseCurrency().id()), false); } } } } KEquityPriceUpdateDlg *q_ptr; Ui::KEquityPriceUpdateDlg *ui; bool m_fUpdateAll; eDialogs::UpdatePrice m_updatingPricePolicy; WebPriceQuote m_webQuote; }; KEquityPriceUpdateDlg::KEquityPriceUpdateDlg(QWidget *parent, const QString& securityId) : QDialog(parent), d_ptr(new KEquityPriceUpdateDlgPrivate(this)) { Q_D(KEquityPriceUpdateDlg); d->init(securityId); } KEquityPriceUpdateDlg::KEquityPriceUpdateDlg(QWidget *parent) : KEquityPriceUpdateDlg(parent, QString()) { } KEquityPriceUpdateDlg::~KEquityPriceUpdateDlg() { Q_D(KEquityPriceUpdateDlg); auto config = KSharedConfig::openConfig(); auto grp = config->group("Equity Price Update"); grp.writeEntry("PriceUpdatingPolicy", static_cast(d->m_updatingPricePolicy)); grp.sync(); delete d; } void KEquityPriceUpdateDlg::logErrorMessage(const QString& message) { logStatusMessage(QString("") + message + QString("")); } void KEquityPriceUpdateDlg::logStatusMessage(const QString& message) { Q_D(KEquityPriceUpdateDlg); d->ui->lbStatus->append(message); } MyMoneyPrice KEquityPriceUpdateDlg::price(const QString& id) const { Q_D(const KEquityPriceUpdateDlg); MyMoneyPrice price; QTreeWidgetItem* item = nullptr; QList foundItems = d->ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL); if (! foundItems.empty()) item = foundItems.at(0); if (item) { MyMoneyMoney rate(item->text(PRICE_COL)); if (!rate.isZero()) { QString id = item->text(KMMID_COL).toUtf8(); // if the ID has a space, then this is TWO ID's, so it's a currency quote if (id.contains(" ")) { QStringList ids = id.split(' ', QString::SkipEmptyParts); QString fromid = ids[0].toUtf8(); QString toid = ids[1].toUtf8(); price = MyMoneyPrice(fromid, toid, QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL)); } else // otherwise, it's a security quote { MyMoneySecurity security = MyMoneyFile::instance()->security(id); price = MyMoneyPrice(id, security.tradingCurrency(), QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL)); } } } return price; } void KEquityPriceUpdateDlg::storePrices() { Q_D(KEquityPriceUpdateDlg); // update the new prices into the equities auto file = MyMoneyFile::instance(); QString name; MyMoneyFileTransaction ft; try { for (auto i = 0; i < d->ui->lvEquityList->invisibleRootItem()->childCount(); ++i) { QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(i); // turn on signals before we modify the last entry in the list file->blockSignals(i < d->ui->lvEquityList->invisibleRootItem()->childCount() - 1); MyMoneyMoney rate(item->text(PRICE_COL)); if (!rate.isZero()) { QString id = item->text(KMMID_COL); QString fromid; QString toid; // if the ID has a space, then this is TWO ID's, so it's a currency quote if (id.contains(QLatin1Char(' '))) { QStringList ids = id.split(QLatin1Char(' '), QString::SkipEmptyParts); fromid = ids.at(0); toid = ids.at(1); name = QString::fromLatin1("%1 --> %2").arg(fromid, toid); } else { // otherwise, it's a security quote MyMoneySecurity security = file->security(id); name = security.name(); fromid = id; toid = security.tradingCurrency(); } // TODO (Ace) Better handling of the case where there is already a price // for this date. Currently, it just overrides the old value. Really it // should check to see if the price is the same and prompt the user. file->addPrice(MyMoneyPrice(fromid, toid, QDate::fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL))); } } ft.commit(); } catch (const MyMoneyException &) { qDebug("Unable to add price information for %s", qPrintable(name)); } } void KEquityPriceUpdateDlg::slotConfigureClicked() { Q_D(KEquityPriceUpdateDlg); QPointer dlg = new EquityPriceUpdateConfDlg(d->m_updatingPricePolicy); if (dlg->exec() == QDialog::Accepted) d->m_updatingPricePolicy = dlg->policy(); delete dlg; } void KEquityPriceUpdateDlg::slotUpdateSelection() { Q_D(KEquityPriceUpdateDlg); // Only enable the update button if there is a selection d->ui->btnUpdateSelected->setEnabled(false); if (! d->ui->lvEquityList->selectedItems().empty()) d->ui->btnUpdateSelected->setEnabled(true); } void KEquityPriceUpdateDlg::slotUpdateSelectedClicked() { Q_D(KEquityPriceUpdateDlg); // disable sorting while the update is running to maintain the current order of items on which // the update process depends and which could be changed with sorting enabled due to the updated values d->ui->lvEquityList->setSortingEnabled(false); auto item = d->ui->lvEquityList->invisibleRootItem()->child(0); auto skipCnt = 1; while (item && !item->isSelected()) { item = d->ui->lvEquityList->invisibleRootItem()->child(skipCnt); ++skipCnt; } d->m_webQuote.setDate(d->ui->m_fromDate->date(), d->ui->m_toDate->date()); if (item) { d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount()); d->ui->prgOnlineProgress->setValue(skipCnt); d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL)); } else { logErrorMessage("No security selected."); } } void KEquityPriceUpdateDlg::slotUpdateAllClicked() { Q_D(KEquityPriceUpdateDlg); // disable sorting while the update is running to maintain the current order of items on which // the update process depends and which could be changed with sorting enabled due to the updated values d->ui->lvEquityList->setSortingEnabled(false); QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(0); if (item) { d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount()); d->ui->prgOnlineProgress->setValue(1); d->m_fUpdateAll = true; d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL)); } else { logErrorMessage("Security list is empty."); } } void KEquityPriceUpdateDlg::slotDateChanged() { Q_D(KEquityPriceUpdateDlg); d->ui->m_fromDate->blockSignals(true); d->ui->m_toDate->blockSignals(true); if (d->ui->m_toDate->date() > QDate::currentDate()) d->ui->m_toDate->setDate(QDate::currentDate()); if (d->ui->m_toDate->date() < d->ui->m_fromDate->date()) d->ui->m_fromDate->setDate(d->ui->m_toDate->date()); d->ui->m_fromDate->blockSignals(false); d->ui->m_toDate->blockSignals(false); } void KEquityPriceUpdateDlg::slotQuoteFailed(const QString& _kmmID, const QString& _webID) { Q_D(KEquityPriceUpdateDlg); auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL); QTreeWidgetItem* item = nullptr; if (! foundItems.empty()) item = foundItems.at(0); // Give the user some options int result; if (_kmmID.contains(" ")) { result = KMessageBox::warningContinueCancel(this, i18n("Failed to retrieve an exchange rate for %1 from %2. It will be skipped this time.", _webID, item->text(SOURCE_COL)), i18n("Price Update Failed")); } else { result = KMessageBox::questionYesNoCancel(this, QString("%1").arg(i18n("Failed to retrieve a quote for %1 from %2. Press No to remove the online price source from this security permanently, Yes to continue updating this security during future price updates or Cancel to stop the current update operation.", _webID, item->text(SOURCE_COL))), i18n("Price Update Failed"), KStandardGuiItem::yes(), KStandardGuiItem::no()); } if (result == KMessageBox::No) { // Disable price updates for this security MyMoneyFileTransaction ft; try { // Get this security (by ID) MyMoneySecurity security = MyMoneyFile::instance()->security(_kmmID.toUtf8()); // Set the quote source to blank security.setValue("kmm-online-source", QString()); security.setValue("kmm-online-quote-system", QString()); // Re-commit the security MyMoneyFile::instance()->modifySecurity(security); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Cannot update security %1: %2", _webID, e.what()) + QString(""), i18n("Price Update Failed")); } } // As long as the user doesn't want to cancel, move on! if (result != KMessageBox::Cancel) { QTreeWidgetItem* next = nullptr; d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); item->setSelected(false); // launch the NEXT one ... in case of m_fUpdateAll == false, we // need to parse the list to find the next selected one next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1); if (!d->m_fUpdateAll) { while (next && !next->isSelected()) { d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1); } } if (next) { d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL)); } else { finishUpdate(); } } else { finishUpdate(); } } void KEquityPriceUpdateDlg::slotReceivedCSVQuote(const QString& _kmmID, const QString& _webID, MyMoneyStatement& st) { Q_D(KEquityPriceUpdateDlg); auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL); QTreeWidgetItem* item = nullptr; if (! foundItems.empty()) item = foundItems.at(0); QTreeWidgetItem* next = nullptr; if (item) { auto file = MyMoneyFile::instance(); MyMoneySecurity fromCurrency, toCurrency; if (!_kmmID.contains(QLatin1Char(' '))) { try { toCurrency = MyMoneyFile::instance()->security(_kmmID); fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } else { QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); if (splitrx.indexIn(_kmmID) != -1) { try { fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8()); toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } } if (d->m_updatingPricePolicy != eDialogs::UpdatePrice::All) { QStringList qSources = WebPriceQuote::quoteSources(); for (auto it = st.m_listPrices.begin(); it != st.m_listPrices.end();) { MyMoneyPrice storedPrice = file->price(toCurrency.id(), fromCurrency.id(), (*it).m_date, true); bool priceValid = storedPrice.isValid(); if (!priceValid) ++it; else { switch(d->m_updatingPricePolicy) { case eDialogs::UpdatePrice::Missing: it = st.m_listPrices.erase(it); break; case eDialogs::UpdatePrice::Downloaded: if (!qSources.contains(storedPrice.source())) it = st.m_listPrices.erase(it); else ++it; break; case eDialogs::UpdatePrice::SameSource: if (storedPrice.source().compare((*it).m_sourceName) != 0) it = st.m_listPrices.erase(it); else ++it; break; case eDialogs::UpdatePrice::Ask: { int result = KMessageBox::questionYesNoCancel(this, i18n("For %1 on %2 price %3 already exists.
" "Do you want to replace it with %4?", storedPrice.from(), storedPrice.date().toString(Qt::ISODate), QString().setNum(storedPrice.rate(storedPrice.to()).toDouble(), 'g', 10), QString().setNum((*it).m_amount.toDouble(), 'g', 10)), i18n("Price Already Exists")); switch(result) { case KStandardGuiItem::Yes: ++it; break; case KStandardGuiItem::No: it = st.m_listPrices.erase(it); break; default: case KStandardGuiItem::Cancel: finishUpdate(); return; break; } break; } default: ++it; break; } } } } if (!st.m_listPrices.isEmpty()) { kmymoney->slotStatementImport(st, true); MyMoneyStatement::Price priceClass; if (st.m_listPrices.first().m_date > st.m_listPrices.last().m_date) priceClass = st.m_listPrices.first(); else priceClass = st.m_listPrices.last(); QDate latestDate = QDate::fromString(item->text(DATE_COL),Qt::ISODate); if (latestDate <= priceClass.m_date && priceClass.m_amount.isPositive()) { item->setText(PRICE_COL, priceClass.m_amount.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision())); item->setText(DATE_COL, priceClass.m_date.toString(Qt::ISODate)); item->setText(SOURCE_COL, priceClass.m_sourceName); } logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID)); // make sure to make OK button available } d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); item->setSelected(false); // launch the NEXT one ... in case of m_fUpdateAll == false, we // need to parse the list to find the next selected one next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1); if (!d->m_fUpdateAll) { while (next && !next->isSelected()) { d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1); } } } else { logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID)); } if (next) { d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL)); } else { finishUpdate(); } } void KEquityPriceUpdateDlg::slotReceivedQuote(const QString& _kmmID, const QString& _webID, const QDate& _date, const double& _price) { Q_D(KEquityPriceUpdateDlg); auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL); QTreeWidgetItem* item = nullptr; if (! foundItems.empty()) item = foundItems.at(0); QTreeWidgetItem* next = 0; if (item) { if (_price > 0.0f && _date.isValid()) { QDate date = _date; if (date > QDate::currentDate()) date = QDate::currentDate(); MyMoneyMoney price = MyMoneyMoney::ONE; QString id = _kmmID.toUtf8(); MyMoneySecurity fromCurrency, toCurrency; if (_kmmID.contains(" ") == 0) { MyMoneySecurity security = MyMoneyFile::instance()->security(id); QString factor = security.value("kmm-online-factor"); if (!factor.isEmpty()) { price = price * MyMoneyMoney(factor); } try { toCurrency = MyMoneyFile::instance()->security(id); fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } else { QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive); if (splitrx.indexIn(_kmmID) != -1) { try { fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8()); toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8()); } catch (const MyMoneyException &) { fromCurrency = toCurrency = MyMoneySecurity(); } } } price *= MyMoneyMoney(_price, MyMoneyMoney::precToDenom(toCurrency.pricePrecision())); item->setText(PRICE_COL, price.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision())); item->setText(DATE_COL, date.toString(Qt::ISODate)); logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID)); // make sure to make OK button available d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } else { logErrorMessage(i18n("Received an invalid price for %1, unable to update.", _webID)); } d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); item->setSelected(false); // launch the NEXT one ... in case of m_fUpdateAll == false, we // need to parse the list to find the next selected one next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1); if (!d->m_fUpdateAll) { while (next && !next->isSelected()) { d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1); next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1); } } } else { logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID)); } if (next) { d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL)); } else { finishUpdate(); } } void KEquityPriceUpdateDlg::finishUpdate() { Q_D(KEquityPriceUpdateDlg); // we've run past the end, reset to the default value. d->m_fUpdateAll = false; // force progress bar to show 100% d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->maximum()); // re-enable the sorting that was disabled during the update process d->ui->lvEquityList->setSortingEnabled(true); } // Make sure, that these definitions are only used within this file // this does not seem to be necessary, but when building RPMs the // build option 'final' is used and all CPP files are concatenated. // So it could well be, that in another CPP file these definitions // are also used. #undef WEBID_COL #undef NAME_COL #undef PRICE_COL #undef DATE_COL #undef KMMID_COL #undef SOURCE_COL diff --git a/kmymoney/dialogs/kfindtransactiondlg_p.h b/kmymoney/dialogs/kfindtransactiondlg_p.h index 78b076622..6d9390acd 100644 --- a/kmymoney/dialogs/kfindtransactiondlg_p.h +++ b/kmymoney/dialogs/kfindtransactiondlg_p.h @@ -1,634 +1,634 @@ /*************************************************************************** kfindtransactiondlg.cpp ------------------- copyright : (C) 2003, 2007 by Thomas Baumgart email : ipwizard@users.sourceforge.net (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * q 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. * * * ***************************************************************************/ #ifndef KFINDTRANSACTIONDLG_P_H #define KFINDTRANSACTIONDLG_P_H #include "kfindtransactiondlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyedit.h" #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "kmymoneyglobalsettings.h" #include "register.h" #include "transaction.h" #include "daterangedlg.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "ui_kfindtransactiondlg.h" #include "widgetenums.h" #include "mymoneyenums.h" class KFindTransactionDlgPrivate { Q_DISABLE_COPY(KFindTransactionDlgPrivate) Q_DECLARE_PUBLIC(KFindTransactionDlg) public: enum opTypeE { addAccountToFilter = 0, addCategoryToFilter, addPayeeToFilter, addTagToFilter }; - KFindTransactionDlgPrivate(KFindTransactionDlg *qq) : + explicit KFindTransactionDlgPrivate(KFindTransactionDlg *qq) : q_ptr(qq), ui(new Ui::KFindTransactionDlg) { } ~KFindTransactionDlgPrivate() { delete ui; } void init(bool withEquityAccounts) { Q_Q(KFindTransactionDlg); m_needReload = false; ui->setupUi(q); m_dateRange = new DateRangeDlg; ui->dateRangeLayout->insertWidget(0, m_dateRange); ui->ButtonGroup1->setId(ui->m_amountButton, 0); ui->ButtonGroup1->setId(ui->m_amountRangeButton, 1); ui->m_register->installEventFilter(q); ui->m_tabWidget->setTabEnabled(ui->m_tabWidget->indexOf(ui->m_resultPage), false); // 'cause we don't have a separate setupTextPage q->connect(ui->m_textEdit, &QLineEdit::textChanged, q, &KFindTransactionDlg::slotUpdateSelections); // if return is pressed trigger a search (slotSearch checks if it's possible to perform the search) q->connect(ui->m_textEdit, &KLineEdit::returnPressed, q, &KFindTransactionDlg::slotSearch); // in case the date selection changes, we update the selection q->connect(m_dateRange, &DateRangeDlg::rangeChanged, q, &KFindTransactionDlg::slotUpdateSelections); setupAccountsPage(withEquityAccounts); setupCategoriesPage(); setupAmountPage(); setupPayeesPage(); setupTagsPage(); setupDetailsPage(); // We don't need to add the default into the list (see ::slotShowHelp() why) // m_helpAnchor[m_ui->m_textTab] = QLatin1String("details.search"); m_helpAnchor[ui->m_accountTab] = QLatin1String("details.search.account"); m_helpAnchor[ui->m_dateTab] = QLatin1String("details.search.date"); m_helpAnchor[ui->m_amountTab] = QLatin1String("details.search.amount"); m_helpAnchor[ui->m_categoryTab] = QLatin1String("details.search.category"); m_helpAnchor[ui->m_payeeTab] = QLatin1String("details.search.payee"); m_helpAnchor[ui->m_tagTab] = QLatin1String("details.search.tag"); //FIXME-ALEX update Help m_helpAnchor[ui->m_detailsTab] = QLatin1String("details.search.details"); // setup the register QList cols { eWidgets::eTransaction::Column::Date, eWidgets::eTransaction::Column::Account, eWidgets::eTransaction::Column::Detail, eWidgets::eTransaction::Column::ReconcileFlag, eWidgets::eTransaction::Column::Payment, eWidgets::eTransaction::Column::Deposit}; ui->m_register->setupRegister(MyMoneyAccount(), cols); ui->m_register->setSelectionMode(QTableWidget::SingleSelection); q->connect(ui->m_register, &KMyMoneyRegister::Register::editTransaction, q, &KFindTransactionDlg::slotSelectTransaction); q->connect(ui->m_register->horizontalHeader(), &QWidget::customContextMenuRequested, q, &KFindTransactionDlg::slotSortOptions); q->slotUpdateSelections(); // setup the connections q->connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, &KFindTransactionDlg::slotSearch); q->connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, q, &KFindTransactionDlg::slotReset); q->connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, ui->m_accountsView, &KMyMoneyAccountSelector::slotSelectAllAccounts); q->connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, ui->m_categoriesView, &KMyMoneyAccountSelector::slotSelectAllAccounts); q->connect(ui->buttonBox->button(QDialogButtonBox::Close), &QAbstractButton::clicked, q, &QObject::deleteLater); q->connect(ui->buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &KFindTransactionDlg::slotShowHelp); // only allow searches when a selection has been made ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); KGuiItem::assign(ui->buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::find()); ui->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip for find transaction apply button", "Search transactions")); q->connect(q, &KFindTransactionDlg::selectionNotEmpty, ui->buttonBox->button(QDialogButtonBox::Apply), &QWidget::setEnabled); // get signal about engine changes q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KFindTransactionDlg::slotRefreshView); q->slotUpdateSelections(); ui->m_textEdit->setFocus(); } /** * q method returns information about the selection state * of the items in the m_accountsView. * * @param view pointer to the listview to scan * * @retval true if all items in the view are marked * @retval false if at least one item is not marked * * @note If the view contains no items the method returns @p true. */ bool allItemsSelected(const QTreeWidgetItem *item) const { QTreeWidgetItem* it_v; for (auto i = 0; i < item->childCount(); ++i) { it_v = item->child(i); if (!(it_v->checkState(0) == Qt::Checked && allItemsSelected(it_v))) { return false; } } return true; } bool allItemsSelected(const QTreeWidget* view) const { QTreeWidgetItem* it_v; for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); if (it_v->flags() & Qt::ItemIsUserCheckable) { if (!(it_v->checkState(0) == Qt::Checked && allItemsSelected(it_v))) { return false; } else { if (!allItemsSelected(it_v)) return false; } } } return true; } void addItemToFilter(const opTypeE op, const QString& id) { switch (op) { case addAccountToFilter: m_filter.addAccount(id); break; case addCategoryToFilter: m_filter.addCategory(id); break; case addPayeeToFilter: m_filter.addPayee(id); break; case addTagToFilter: m_filter.addTag(id); break; } } void scanCheckListItems(const QTreeWidgetItem* item, const opTypeE op) { QTreeWidgetItem* it_v; for (auto i = 0; i < item->childCount(); ++i) { it_v = item->child(i); QVariant idData = it_v->data(0, Qt::UserRole); if (it_v->flags() & Qt::ItemIsUserCheckable) { if (it_v->checkState(0) == Qt::Checked) addItemToFilter(op, idData.toString()); } scanCheckListItems(it_v, op); } } void scanCheckListItems(const QTreeWidget* view, const opTypeE op) { QTreeWidgetItem* it_v; for (auto i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); QVariant idData = it_v->data(0, Qt::UserRole); if (it_v->flags() & Qt::ItemIsUserCheckable) { if (it_v->checkState(0) == Qt::Checked) { addItemToFilter(op, idData.toString()); } } scanCheckListItems(it_v, op); } } void selectAllItems(QTreeWidget* view, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); if (it_v->flags() & Qt::ItemIsUserCheckable) { it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); } selectAllSubItems(it_v, state); } Q_Q(KFindTransactionDlg); q->slotUpdateSelections(); } void selectItems(QTreeWidget* view, const QStringList& list, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); QVariant idData = it_v->data(0, Qt::UserRole); if (it_v->flags() & Qt::ItemIsUserCheckable && list.contains(idData.toString())) { it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); } selectSubItems(it_v, list, state); } Q_Q(KFindTransactionDlg); q->slotUpdateSelections(); } void selectAllSubItems(QTreeWidgetItem* item, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < item->childCount(); ++i) { it_v = item->child(i); it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); selectAllSubItems(it_v, state); } } void selectSubItems(QTreeWidgetItem* item, const QStringList& list, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < item->childCount(); ++i) { it_v = item->child(i); QVariant idData = it_v->data(0, Qt::UserRole); if (list.contains(idData.toString())) it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); selectSubItems(it_v, list, state); } } /** * q method loads the register with the matching transactions */ void loadView() { // setup sort order ui->m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView()); // clear out old data ui->m_register->clear(); // retrieve the list from the engine MyMoneyFile::instance()->transactionList(m_transactionList, m_filter); // create the elements for the register QList >::const_iterator it; QMapuniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = MyMoneyFile::instance()->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); { // debug stuff if (split.shares().isNegative()) { payment += split.shares().abs(); } else { deposit += split.shares().abs(); } } } // add the group markers ui->m_register->addGroupMarkers(); // sort the transactions according to the sort setting ui->m_register->sortItems(); // remove trailing and adjacent markers ui->m_register->removeUnwantedGroupMarkers(); // turn on the ledger lens for the register ui->m_register->setLedgerLensForced(); ui->m_register->updateRegister(true); ui->m_register->setFocusToTop(); ui->m_register->selectItem(ui->m_register->focusItem()); #ifdef KMM_DEBUG ui->m_foundText->setText(i18np("Found %1 matching transaction (D %2 / P %3 = %4)", "Found %1 matching transactions (D %2 / P %3 = %4)", splitCount, deposit.formatMoney("", 2), payment.formatMoney("", 2), (deposit - payment).formatMoney("", 2))); #else ui->m_foundText->setText(i18np("Found %1 matching transaction", "Found %1 matching transactions", splitCount)); #endif ui->m_tabWidget->setTabEnabled(ui->m_tabWidget->indexOf(ui->m_resultPage), true); ui->m_tabWidget->setCurrentIndex(ui->m_tabWidget->indexOf(ui->m_resultPage)); Q_Q(KFindTransactionDlg); QTimer::singleShot(10, q, SLOT(slotRightSize())); } /** * q method loads the m_tagsView with the tags name * found in the engine. */ void loadTags() { MyMoneyFile* file = MyMoneyFile::instance(); QList list; QList::Iterator it_l; list = file->tagList(); // load view for (it_l = list.begin(); it_l != list.end(); ++it_l) { auto item = new QTreeWidgetItem(ui->m_tagsView); item->setText(0, (*it_l).name()); item->setData(0, Qt::UserRole, (*it_l).id()); item->setCheckState(0, Qt::Checked); } } /** * q method loads the m_payeesView with the payees name * found in the engine. */ void loadPayees() { MyMoneyFile* file = MyMoneyFile::instance(); QList list; QList::Iterator it_l; list = file->payeeList(); // load view for (it_l = list.begin(); it_l != list.end(); ++it_l) { auto item = new QTreeWidgetItem(ui->m_payeesView); item->setText(0, (*it_l).name()); item->setData(0, Qt::UserRole, (*it_l).id()); item->setCheckState(0, Qt::Checked); } } void setupFilter() { m_filter.clear(); // Text tab if (!ui->m_textEdit->text().isEmpty()) { QRegExp exp(ui->m_textEdit->text(), ui->m_caseSensitive->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive, !ui->m_regExp->isChecked() ? QRegExp::Wildcard : QRegExp::RegExp); m_filter.setTextFilter(exp, ui->m_textNegate->currentIndex() != 0); } // Account tab if (!ui->m_accountsView->allItemsSelected()) { // retrieve a list of selected accounts QStringList list; ui->m_accountsView->selectedItems(list); // if we're not in expert mode, we need to make sure // that all stock accounts for the selected investment // account are also selected if (!KMyMoneyGlobalSettings::expertMode()) { QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == eMyMoney::Account::Type::Investment) { for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) { if (!list.contains(*it_b)) { missing.append(*it_b); } } } } list += missing; } m_filter.addAccount(list); } // Date tab if ((int)m_dateRange->dateRange() != 0) { m_filter.setDateFilter(m_dateRange->fromDate(), m_dateRange->toDate()); } // Amount tab if ((ui->m_amountButton->isChecked() && ui->m_amountEdit->isValid())) { m_filter.setAmountFilter(ui->m_amountEdit->value(), ui->m_amountEdit->value()); } else if ((ui->m_amountRangeButton->isChecked() && (ui->m_amountFromEdit->isValid() || ui->m_amountToEdit->isValid()))) { MyMoneyMoney from(MyMoneyMoney::minValue), to(MyMoneyMoney::maxValue); if (ui->m_amountFromEdit->isValid()) from = ui->m_amountFromEdit->value(); if (ui->m_amountToEdit->isValid()) to = ui->m_amountToEdit->value(); m_filter.setAmountFilter(from, to); } // Categories tab if (!ui->m_categoriesView->allItemsSelected()) { m_filter.addCategory(ui->m_categoriesView->selectedItems()); } // Tags tab if (ui->m_emptyTagsButton->isChecked()) { m_filter.addTag(QString()); } else if (!allItemsSelected(ui->m_tagsView)) { scanCheckListItems(ui->m_tagsView, addTagToFilter); } // Payees tab if (ui->m_emptyPayeesButton->isChecked()) { m_filter.addPayee(QString()); } else if (!allItemsSelected(ui->m_payeesView)) { scanCheckListItems(ui->m_payeesView, addPayeeToFilter); } // Details tab if (ui->m_typeBox->currentIndex() != 0) m_filter.addType(ui->m_typeBox->currentIndex()); if (ui->m_stateBox->currentIndex() != 0) m_filter.addState(ui->m_stateBox->currentIndex()); if (ui->m_validityBox->currentIndex() != 0) m_filter.addValidity(ui->m_validityBox->currentIndex()); if (ui->m_nrButton->isChecked() && !ui->m_nrEdit->text().isEmpty()) m_filter.setNumberFilter(ui->m_nrEdit->text(), ui->m_nrEdit->text()); if (ui->m_nrRangeButton->isChecked() && (!ui->m_nrFromEdit->text().isEmpty() || !ui->m_nrToEdit->text().isEmpty())) { m_filter.setNumberFilter(ui->m_nrFromEdit->text(), ui->m_nrToEdit->text()); } } void setupDetailsPage() { Q_Q(KFindTransactionDlg); q->connect(ui->m_typeBox, static_cast(&QComboBox::activated), q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_stateBox, static_cast(&QComboBox::activated), q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_validityBox, static_cast(&QComboBox::activated), q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_nrButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotNrSelected); q->connect(ui->m_nrRangeButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotNrRangeSelected); q->connect(ui->m_nrEdit, &QLineEdit::textChanged, q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_nrFromEdit, &QLineEdit::textChanged, q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_nrToEdit, &QLineEdit::textChanged, q, &KFindTransactionDlg::slotUpdateSelections); ui->m_nrButton->setChecked(true); q->slotNrSelected(); } void setupTagsPage() { Q_Q(KFindTransactionDlg); ui->m_tagsView->setSelectionMode(QAbstractItemView::SingleSelection); ui->m_tagsView->header()->hide(); ui->m_tagsView->setAlternatingRowColors(true); loadTags(); ui->m_tagsView->sortItems(0, Qt::AscendingOrder); ui->m_emptyTagsButton->setCheckState(Qt::Unchecked); q->connect(ui->m_allTagsButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotSelectAllTags); q->connect(ui->m_clearTagsButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotDeselectAllTags); q->connect(ui->m_emptyTagsButton, &QCheckBox::stateChanged, q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_tagsView, &QTreeWidget::itemChanged, q, &KFindTransactionDlg::slotUpdateSelections); } void setupPayeesPage() { Q_Q(KFindTransactionDlg); ui->m_payeesView->setSelectionMode(QAbstractItemView::SingleSelection); ui->m_payeesView->header()->hide(); ui->m_payeesView->setAlternatingRowColors(true); loadPayees(); ui->m_payeesView->sortItems(0, Qt::AscendingOrder); ui->m_emptyPayeesButton->setCheckState(Qt::Unchecked); q->connect(ui->m_allPayeesButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotSelectAllPayees); q->connect(ui->m_clearPayeesButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotDeselectAllPayees); q->connect(ui->m_emptyPayeesButton, &QCheckBox::stateChanged, q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_payeesView, &QTreeWidget::itemChanged, q, &KFindTransactionDlg::slotUpdateSelections); } void setupAmountPage() { Q_Q(KFindTransactionDlg); q->connect(ui->m_amountButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotAmountSelected); q->connect(ui->m_amountRangeButton, &QAbstractButton::clicked, q, &KFindTransactionDlg::slotAmountRangeSelected); q->connect(ui->m_amountEdit, &KMyMoneyEdit::textChanged, q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_amountFromEdit, &KMyMoneyEdit::textChanged, q, &KFindTransactionDlg::slotUpdateSelections); q->connect(ui->m_amountToEdit, &KMyMoneyEdit::textChanged, q, &KFindTransactionDlg::slotUpdateSelections); ui->m_amountButton->setChecked(true); q->slotAmountSelected(); } void setupCategoriesPage() { Q_Q(KFindTransactionDlg); ui->m_categoriesView->setSelectionMode(QTreeWidget::MultiSelection); AccountSet categorySet; categorySet.addAccountGroup(eMyMoney::Account::Type::Income); categorySet.addAccountGroup(eMyMoney::Account::Type::Expense); categorySet.load(ui->m_categoriesView); q->connect(ui->m_categoriesView, &KMyMoneyAccountSelector::stateChanged, q, &KFindTransactionDlg::slotUpdateSelections); } void setupAccountsPage(bool withEquityAccounts) { Q_Q(KFindTransactionDlg); ui->m_accountsView->setSelectionMode(QTreeWidget::MultiSelection); AccountSet accountSet; accountSet.addAccountGroup(eMyMoney::Account::Type::Asset); accountSet.addAccountGroup(eMyMoney::Account::Type::Liability); if (withEquityAccounts) accountSet.addAccountGroup(eMyMoney::Account::Type::Equity); //set the accountset to show closed account if the settings say so accountSet.setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts()); accountSet.load(ui->m_accountsView); q->connect(ui->m_accountsView, &KMyMoneyAccountSelector::stateChanged, q, &KFindTransactionDlg::slotUpdateSelections); } KFindTransactionDlg *q_ptr; Ui::KFindTransactionDlg *ui; QDate m_startDates[(int)eMyMoney::TransactionFilter::Date::LastDateItem]; QDate m_endDates[(int)eMyMoney::TransactionFilter::Date::LastDateItem]; /** * q member holds a list of all transactions matching the filter criteria */ QList > m_transactionList; MyMoneyTransactionFilter m_filter; QMap m_helpAnchor; bool m_needReload; DateRangeDlg *m_dateRange; }; #endif diff --git a/kmymoney/dialogs/kgeneratesqldlg.cpp b/kmymoney/dialogs/kgeneratesqldlg.cpp index 2e2516535..c0f7fb292 100644 --- a/kmymoney/dialogs/kgeneratesqldlg.cpp +++ b/kmymoney/dialogs/kgeneratesqldlg.cpp @@ -1,340 +1,340 @@ /*************************************************************************** kgeneratesqldlg.cpp ------------------- copyright : (C) 2009 by Tony Bloomfield (C) 2017 by Ł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. * * * ***************************************************************************/ #include "kgeneratesqldlg.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // System includes // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kgeneratesqldlg.h" #include "mymoneyfile.h" #include "storage/mymoneyseqaccessmgr.h" #include "kguiutils.h" #include "misc/platformtools.h" #include "mymoneydbdriver.h" #include "mymoneydbdef.h" class KGenerateSqlDlgPrivate { Q_DISABLE_COPY(KGenerateSqlDlgPrivate) Q_DECLARE_PUBLIC(KGenerateSqlDlg) public: - KGenerateSqlDlgPrivate(KGenerateSqlDlg *qq) : + explicit KGenerateSqlDlgPrivate(KGenerateSqlDlg *qq) : q_ptr(qq), ui(new Ui::KGenerateSqlDlg) { } ~KGenerateSqlDlgPrivate() { delete ui; } void init() { Q_Q(KGenerateSqlDlg); ui->setupUi(q); m_createTablesButton = ui->buttonBox->addButton(i18n("Create Tables"), QDialogButtonBox::ButtonRole::AcceptRole); m_saveSqlButton = ui->buttonBox->addButton(i18n("Save SQL"), QDialogButtonBox::ButtonRole::ActionRole); Q_ASSERT(m_createTablesButton); Q_ASSERT(m_saveSqlButton); q->connect(ui->buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); q->connect(ui->buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); initializeForm(); } void initializeForm() { Q_Q(KGenerateSqlDlg); m_requiredFields = nullptr; // at this point, we don't know which fields are required, so disable everything but the list m_saveSqlButton->setEnabled(false); m_createTablesButton->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->urlSqlite->clear(); ui->textDbName->clear(); ui->textHostName->clear(); ui->textPassword->clear(); ui->textUserName->clear(); ui->textSQL->clear(); ui->urlSqlite->setEnabled(false); ui->textDbName->setEnabled(false); ui->textHostName->setEnabled(false); ui->textPassword->setEnabled(false); ui->textUserName->setEnabled(false); ui->textSQL->setEnabled(false); q->connect(ui->buttonBox->button(QDialogButtonBox::Help), &QPushButton::clicked, q, &KGenerateSqlDlg::slotHelp); } QString selectedDriver() { auto drivers = ui->listDrivers->selectedItems(); if (drivers.count() != 1) { return QString(); } return drivers[0]->text().section(' ', 0, 0); } KGenerateSqlDlg *q_ptr; Ui::KGenerateSqlDlg *ui; QPushButton *m_createTablesButton; QPushButton *m_saveSqlButton; QList m_supportedDrivers; //MyMoneyDbDrivers m_map; std::unique_ptr m_requiredFields; bool m_sqliteSelected; QExplicitlySharedDataPointer m_dbDriver; QString m_dbName; MyMoneySeqAccessMgr* m_storage; bool m_mustDetachStorage; }; KGenerateSqlDlg::KGenerateSqlDlg(QWidget *parent) : QDialog(parent), d_ptr(new KGenerateSqlDlgPrivate(this)) { Q_D(KGenerateSqlDlg); d->init(); } KGenerateSqlDlg::~KGenerateSqlDlg() { Q_D(KGenerateSqlDlg); delete d; } int KGenerateSqlDlg::exec() { Q_D(KGenerateSqlDlg); // list drivers supported by KMM auto map = MyMoneyDbDriver::driverMap(); // list drivers installed on system auto list = QSqlDatabase::drivers(); // join the two QStringList::Iterator it = list.begin(); while (it != list.end()) { QString dname = *it; if (map.keys().contains(dname)) { // only keep if driver is supported dname = dname + " - " + map[dname]; d->m_supportedDrivers.append(dname); } ++it; } if (d->m_supportedDrivers.count() == 0) { // why does KMessageBox not have a standard dialog with Help button? if ((KMessageBox::questionYesNo(this, i18n("In order to use a database, you need to install some additional software. Click Help for more information"), i18n("No Qt SQL Drivers"), KStandardGuiItem::help(), KStandardGuiItem::cancel())) == KMessageBox::Yes) { // Yes stands in for help here KHelpClient::invokeHelp("details.database.usage"); } return (1); } d->ui->listDrivers->clear(); d->ui->listDrivers->addItems(d->m_supportedDrivers); connect(d->ui->listDrivers, &QListWidget::itemSelectionChanged, this, &KGenerateSqlDlg::slotdriverSelected); return (QDialog::exec()); } void KGenerateSqlDlg::slotcreateTables() { Q_D(KGenerateSqlDlg); if (d->m_sqliteSelected) { d->m_dbName = d->ui->urlSqlite->text(); } else { d->m_dbName = d->ui->textDbName->text(); } // check that the database has been pre-created { // all queries etc. must be in a block - see 'remove database' API doc Q_ASSERT(!d->selectedDriver().isEmpty()); QSqlDatabase dbase = QSqlDatabase::addDatabase(d->selectedDriver(), "creation"); dbase.setHostName(d->ui->textHostName->text()); dbase.setDatabaseName(d->m_dbName); dbase.setUserName(d->ui->textUserName->text()); dbase.setPassword(d->ui->textPassword->text()); if (!dbase.open()) { KMessageBox::error(this, i18n("Unable to open database.\n" "You must use an SQL CREATE DATABASE statement before creating the tables.\n") ); return; } QSqlQuery q(dbase); QString message(i18n("Tables successfully created")); QStringList commands = d->ui->textSQL->toPlainText().split('\n'); QStringList::ConstIterator cit; for (cit = commands.constBegin(); cit != commands.constEnd(); ++cit) { if (!(*cit).isEmpty()) { //qDebug() << "exec" << *cit; q.prepare(*cit); if (!q.exec()) { QSqlError e = q.lastError(); message = i18n("Creation failed executing statement" "\nExecuted: %1" "\nError No %2: %3", q.executedQuery(), e.number(), e.text()); break; } } } KMessageBox::information(this, message); } QSqlDatabase::removeDatabase("creation"); auto okButton = d->ui->buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(okButton); okButton->setEnabled(true); } void KGenerateSqlDlg::slotsaveSQL() { Q_D(KGenerateSqlDlg); auto fileName = QFileDialog::getSaveFileName( this, i18n("Select output file"), QString(), QString()); if (fileName.isEmpty()) return; QFile out(fileName); if (!out.open(QIODevice::WriteOnly)) return; QTextStream s(&out); MyMoneyDbDef db; s << d->ui->textSQL->toPlainText(); out.close(); auto okButton = d->ui->buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(okButton); okButton->setEnabled(true); } void KGenerateSqlDlg::slotdriverSelected() { Q_D(KGenerateSqlDlg); const auto driverName = d->selectedDriver(); if (driverName.isEmpty()) { d->initializeForm(); return; } d->m_dbDriver = MyMoneyDbDriver::create(driverName); if (!d->m_dbDriver->isTested()) { int rc = KMessageBox::warningContinueCancel(0, i18n("Database type %1 has not been fully tested in a KMyMoney environment.\n" "Please make sure you have adequate backups of your data.\n" "Please report any problems to the developer mailing list at " "kmymoney-devel@kde.org", driverName), ""); if (rc == KMessageBox::Cancel) { d->ui->listDrivers->clearSelection(); d->initializeForm(); return; } } d->m_requiredFields.reset(new KMandatoryFieldGroup(this)); // currently, only sqlite need an external file if (d->m_dbDriver->requiresExternalFile()) { d->m_sqliteSelected = true; d->ui->urlSqlite->setMode(KFile::Mode::File); d->ui->urlSqlite->setEnabled(true); d->m_requiredFields->add(d->ui->urlSqlite); d->ui->textDbName->setEnabled(false); d->ui->textHostName->setEnabled(false); d->ui->textUserName->setEnabled(false); } else { // not sqlite3 d->m_sqliteSelected = false; d->ui->urlSqlite->setEnabled(false); d->ui->textDbName->setEnabled(true); d->ui->textHostName->setEnabled(true); d->ui->textUserName->setEnabled(true); d->m_requiredFields->add(d->ui->textDbName); d->m_requiredFields->add(d->ui->textHostName); d->m_requiredFields->add(d->ui->textUserName); d->ui->textDbName->setText("KMyMoney"); d->ui->textHostName->setText("localhost"); d->ui->textUserName->setText(""); d->ui->textUserName->setText(platformTools::osUsername()); d->ui->textPassword->setText(""); } d->ui->textPassword->setEnabled(d->m_dbDriver->isPasswordSupported()); d->m_requiredFields->setOkButton(d->m_createTablesButton); d->ui->textSQL->setEnabled(true); // check if we have a storage; if not, create a skeleton one // we need a storage for MyMoneyDbDef to generate standard accounts d->m_storage = new MyMoneySeqAccessMgr; d->m_mustDetachStorage = true; try { MyMoneyFile::instance()->attachStorage(d->m_storage); } catch (const MyMoneyException &) { d->m_mustDetachStorage = false; // there is already a storage attached } MyMoneyDbDef db; d->ui->textSQL->setText (db.generateSQL(d->m_dbDriver)); if (d->m_mustDetachStorage) { MyMoneyFile::instance()->detachStorage(); } delete d->m_storage; d->m_saveSqlButton->setEnabled(true); connect(d->m_saveSqlButton, &QPushButton::clicked, this, &KGenerateSqlDlg::slotsaveSQL); connect(d->m_createTablesButton, &QPushButton::clicked, this, &KGenerateSqlDlg::slotcreateTables); } void KGenerateSqlDlg::slotHelp() { KHelpClient::invokeHelp("details.database.generatesql"); } diff --git a/kmymoney/dialogs/kgncimportoptionsdlg.cpp b/kmymoney/dialogs/kgncimportoptionsdlg.cpp index 5cbba46d7..08bf0915d 100644 --- a/kmymoney/dialogs/kgncimportoptionsdlg.cpp +++ b/kmymoney/dialogs/kgncimportoptionsdlg.cpp @@ -1,190 +1,190 @@ /*************************************************************************** kgncimportoptions.cpp ------------------- copyright : (C) 2005 by Tony Bloomfield (C) 2017 by Ł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. * * * ***************************************************************************/ #include "kgncimportoptionsdlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kgncimportoptionsdlg.h" class KGncImportOptionsDlgPrivate { Q_DISABLE_COPY(KGncImportOptionsDlgPrivate) Q_DECLARE_PUBLIC(KGncImportOptionsDlg) public: - KGncImportOptionsDlgPrivate(KGncImportOptionsDlg *qq) : + explicit KGncImportOptionsDlgPrivate(KGncImportOptionsDlg *qq) : q_ptr(qq), ui(new Ui::KGncImportOptionsDlg), m_localeCodec(nullptr) { } ~KGncImportOptionsDlgPrivate() { delete ui; } void init() { Q_Q(KGncImportOptionsDlg); ui->setupUi(q); ui->buttonInvestGroup->setId(ui->radioInvest1, 0); // one invest acct per stock ui->buttonInvestGroup->setId(ui->radioInvest2, 1); // one invest acct for all stocks ui->buttonInvestGroup->setId(ui->radioInvest3, 2); // prompt for each stock ui->buttonGroup5->setExclusive(false); ui->checkFinanceQuote->setChecked(true); ui->buttonGroup2->setExclusive(false); ui->checkSchedules->setChecked(false); buildCodecList(); // build list of codecs and insert into combo box ui->buttonGroup4->setExclusive(false); ui->checkDecode->setChecked(false); ui->comboDecode->setEnabled(false); ui->buttonGroup18->setExclusive(false); ui->checkTxNotes->setChecked(false); ui->buttonGroup3->setExclusive(false); ui->checkDebugGeneral->setChecked(false); ui->checkDebugXML->setChecked(false); ui->checkAnonymize->setChecked(false); q->connect(ui->checkDecode, &QAbstractButton::toggled, q, &KGncImportOptionsDlg::slotDecodeOptionChanged); q->connect(ui->buttonBox, &QDialogButtonBox::helpRequested, q, &KGncImportOptionsDlg::slotHelp); } void buildCodecList() { m_localeCodec = QTextCodec::codecForLocale(); auto codecList = QTextCodec::availableCodecs(); QList::ConstIterator itc; for (itc = codecList.constBegin(); itc != codecList.constEnd(); ++itc) { if (*itc == m_localeCodec) ui->comboDecode->insertItem(0, QString(*itc)); else ui->comboDecode->insertItem(9999, QString(*itc)); } } KGncImportOptionsDlg *q_ptr; Ui::KGncImportOptionsDlg *ui; QTextCodec *m_localeCodec; }; KGncImportOptionsDlg::KGncImportOptionsDlg(QWidget *parent) : QDialog(parent), d_ptr(new KGncImportOptionsDlgPrivate(this)) { Q_D(KGncImportOptionsDlg); d->init(); } KGncImportOptionsDlg::~KGncImportOptionsDlg() { Q_D(KGncImportOptionsDlg); delete d; } // enable the combo box for selection if required void KGncImportOptionsDlg::slotDecodeOptionChanged(bool isOn) { Q_D(KGncImportOptionsDlg); if (isOn) { d->ui->comboDecode->setEnabled(true); d->ui->comboDecode->setCurrentItem(0); } else { d->ui->comboDecode->setEnabled(false); } } int KGncImportOptionsDlg::investmentOption() const { Q_D(const KGncImportOptionsDlg); return (d->ui->buttonInvestGroup->checkedId()); }; bool KGncImportOptionsDlg::quoteOption() const { Q_D(const KGncImportOptionsDlg); return (d->ui->checkFinanceQuote->isChecked()); }; bool KGncImportOptionsDlg::scheduleOption() const { Q_D(const KGncImportOptionsDlg); return (d->ui->checkSchedules->isChecked()); }; // return selected codec or 0 QTextCodec* KGncImportOptionsDlg::decodeOption() { Q_D(const KGncImportOptionsDlg); if (!d->ui->checkDecode->isChecked()) { return nullptr; } else { return (QTextCodec::codecForName(d->ui->comboDecode->currentText().toUtf8())); } } bool KGncImportOptionsDlg::txNotesOption() const { Q_D(const KGncImportOptionsDlg); return (d->ui->checkTxNotes->isChecked()); } bool KGncImportOptionsDlg::generalDebugOption() const { Q_D(const KGncImportOptionsDlg); return (d->ui->checkDebugGeneral->isChecked()); } bool KGncImportOptionsDlg::xmlDebugOption() const { Q_D(const KGncImportOptionsDlg); return (d->ui->checkDebugXML->isChecked()); } bool KGncImportOptionsDlg::anonymizeOption() const { Q_D(const KGncImportOptionsDlg); return (d->ui->checkAnonymize->isChecked()); } void KGncImportOptionsDlg::slotHelp() { KHelpClient::invokeHelp("details.impexp.gncoptions"); } diff --git a/kmymoney/dialogs/kmymoneypricedlg.cpp b/kmymoney/dialogs/kmymoneypricedlg.cpp index ddc665928..049d02606 100644 --- a/kmymoney/dialogs/kmymoneypricedlg.cpp +++ b/kmymoney/dialogs/kmymoneypricedlg.cpp @@ -1,351 +1,351 @@ /*************************************************************************** kmymoneypricedlg.cpp ------------------- begin : Wed Nov 24 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Ł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. * * * ***************************************************************************/ #include "kmymoneypricedlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kmymoneypricedlg.h" #include "ui_kupdatestockpricedlg.h" #include "kupdatestockpricedlg.h" #include "kcurrencycalculator.h" #include "mymoneyprice.h" #include "kequitypriceupdatedlg.h" #include "kmymoneycurrencyselector.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneymoney.h" #include "kmymoneyutils.h" #include "kpricetreeitem.h" #include "icons/icons.h" using namespace Icons; class KMyMoneyPriceDlgPrivate { Q_DISABLE_COPY(KMyMoneyPriceDlgPrivate) Q_DECLARE_PUBLIC(KMyMoneyPriceDlg) public: - KMyMoneyPriceDlgPrivate(KMyMoneyPriceDlg *qq) : + explicit KMyMoneyPriceDlgPrivate(KMyMoneyPriceDlg *qq) : q_ptr(qq), ui(new Ui::KMyMoneyPriceDlg), m_searchWidget(nullptr) { } ~KMyMoneyPriceDlgPrivate() { delete ui; } KMyMoneyPriceDlg *q_ptr; Ui::KMyMoneyPriceDlg *ui; QTreeWidgetItem* m_currentItem; /** * Search widget for the list */ KTreeWidgetSearchLineWidget* m_searchWidget; QMap m_stockNameMap; }; KMyMoneyPriceDlg::KMyMoneyPriceDlg(QWidget* parent) : QDialog(parent), d_ptr(new KMyMoneyPriceDlgPrivate(this)) { Q_D(KMyMoneyPriceDlg); d->ui->setupUi(this); // create the searchline widget // and insert it into the existing layout d->m_searchWidget = new KTreeWidgetSearchLineWidget(this, d->ui->m_priceList); d->m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); d->ui->m_listLayout->insertWidget(0, d->m_searchWidget); d->ui->m_priceList->header()->setSortIndicator(0, Qt::AscendingOrder); d->ui->m_priceList->header()->setStretchLastSection(true); d->ui->m_priceList->setContextMenuPolicy(Qt::CustomContextMenu); d->ui->m_deleteButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); d->ui->m_newButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentNew])); d->ui->m_editButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit])); d->ui->m_onlineQuoteButton->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewInvestment], g_Icons[Icon::Download])); connect(d->ui->m_editButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotEditPrice); connect(d->ui->m_deleteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotDeletePrice); connect(d->ui->m_newButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotNewPrice); connect(d->ui->m_priceList, &QTreeWidget::itemSelectionChanged, this, &KMyMoneyPriceDlg::slotSelectPrice); connect(d->ui->m_onlineQuoteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotOnlinePriceUpdate); connect(d->ui->m_priceList, &QWidget::customContextMenuRequested, this, &KMyMoneyPriceDlg::slotOpenContextMenu); connect(d->ui->m_showAllPrices, &QAbstractButton::toggled, this, &KMyMoneyPriceDlg::slotLoadWidgets); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneyPriceDlg::slotLoadWidgets); slotLoadWidgets(); slotSelectPrice(); } KMyMoneyPriceDlg::~KMyMoneyPriceDlg() { Q_D(KMyMoneyPriceDlg); delete d; } void KMyMoneyPriceDlg::slotLoadWidgets() { Q_D(KMyMoneyPriceDlg); auto file = MyMoneyFile::instance(); //clear the list and disable the sorting while it loads the widgets, for performance d->ui->m_priceList->setSortingEnabled(false); d->ui->m_priceList->clear(); d->m_stockNameMap.clear(); //load the currencies for investments, which we'll need later QList accList; file->accountList(accList); QList::const_iterator acc_it; for (acc_it = accList.constBegin(); acc_it != accList.constEnd(); ++acc_it) { if ((*acc_it).isInvest()) { if (d->m_stockNameMap.contains((*acc_it).currencyId())) { d->m_stockNameMap[(*acc_it).currencyId()] = QString(d->m_stockNameMap.value((*acc_it).currencyId()) + ", " + (*acc_it).name()); } else { d->m_stockNameMap[(*acc_it).currencyId()] = (*acc_it).name(); } } } //get the price list MyMoneyPriceList list = file->priceList(); MyMoneyPriceList::ConstIterator it_allPrices; for (it_allPrices = list.constBegin(); it_allPrices != list.constEnd(); ++it_allPrices) { MyMoneyPriceEntries::ConstIterator it_priceItem; if (d->ui->m_showAllPrices->isChecked()) { for (it_priceItem = (*it_allPrices).constBegin(); it_priceItem != (*it_allPrices).constEnd(); ++it_priceItem) { loadPriceItem(*it_priceItem); } } else { //if it doesn't show all prices, it only shows the most recent occurrence for each price if ((*it_allPrices).count() > 0) { //the prices for each currency are ordered by date in ascending order //it gets the last item of the item, which is supposed to be the most recent price it_priceItem = (*it_allPrices).constEnd(); --it_priceItem; loadPriceItem(*it_priceItem); } } } //reenable sorting and sort by the commodity column d->ui->m_priceList->setSortingEnabled(true); d->ui->m_priceList->sortByColumn(KPriceTreeItem::ePriceCommodity); //update the search widget so the list gets refreshed correctly if it was being filtered if (!d->m_searchWidget->searchLine()->text().isEmpty()) d->m_searchWidget->searchLine()->updateSearch(d->m_searchWidget->searchLine()->text()); } QTreeWidgetItem* KMyMoneyPriceDlg::loadPriceItem(const MyMoneyPrice& basePrice) { Q_D(KMyMoneyPriceDlg); MyMoneySecurity from, to; auto price = MyMoneyPrice(basePrice); auto priceTreeItem = new KPriceTreeItem(d->ui->m_priceList); if (!price.isValid()) price = MyMoneyFile::instance()->price(price.from(), price.to(), price.date()); if (price.isValid()) { QString priceBase = price.to(); from = MyMoneyFile::instance()->security(price.from()); to = MyMoneyFile::instance()->security(price.to()); if (!to.isCurrency()) { from = MyMoneyFile::instance()->security(price.to()); to = MyMoneyFile::instance()->security(price.from()); priceBase = price.from(); } priceTreeItem->setData(KPriceTreeItem::ePriceCommodity, Qt::UserRole, QVariant::fromValue(price)); priceTreeItem->setText(KPriceTreeItem::ePriceCommodity, (from.isCurrency()) ? from.id() : from.tradingSymbol()); priceTreeItem->setText(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id())); priceTreeItem->setToolTip(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id())); priceTreeItem->setText(KPriceTreeItem::ePriceCurrency, to.id()); priceTreeItem->setText(KPriceTreeItem::ePriceDate, QLocale().toString(price.date(), QLocale::ShortFormat)); priceTreeItem->setData(KPriceTreeItem::ePriceDate, KPriceTreeItem::OrderRole, QVariant(price.date())); priceTreeItem->setText(KPriceTreeItem::ePricePrice, price.rate(priceBase).formatMoney("", from.pricePrecision())); priceTreeItem->setTextAlignment(KPriceTreeItem::ePricePrice, Qt::AlignRight | Qt::AlignVCenter); priceTreeItem->setData(KPriceTreeItem::ePricePrice, KPriceTreeItem::OrderRole, QVariant::fromValue(price.rate(priceBase))); priceTreeItem->setText(KPriceTreeItem::ePriceSource, price.source()); } return priceTreeItem; } void KMyMoneyPriceDlg::slotSelectPrice() { Q_D(KMyMoneyPriceDlg); QTreeWidgetItem* item = 0; if (d->ui->m_priceList->selectedItems().count() > 0) { item = d->ui->m_priceList->selectedItems().at(0); } d->m_currentItem = item; d->ui->m_editButton->setEnabled(item != 0); bool deleteEnabled = (item != 0); //if one of the selected entries is a default, then deleting is disabled QList itemsList = d->ui->m_priceList->selectedItems(); QList::const_iterator item_it; for (item_it = itemsList.constBegin(); item_it != itemsList.constEnd(); ++item_it) { MyMoneyPrice price = (*item_it)->data(0, Qt::UserRole).value(); if (price.source() == "KMyMoney") deleteEnabled = false; } d->ui->m_deleteButton->setEnabled(deleteEnabled); // Modification of automatically added entries is not allowed // Multiple entries cannot be edited at once if (item) { MyMoneyPrice price = item->data(0, Qt::UserRole).value(); if (price.source() == "KMyMoney" || itemsList.count() > 1) d->ui->m_editButton->setEnabled(false); emit selectObject(price); } } void KMyMoneyPriceDlg::slotNewPrice() { Q_D(KMyMoneyPriceDlg); QPointer dlg = new KUpdateStockPriceDlg(this); try { auto item = d->ui->m_priceList->currentItem(); if (item) { MyMoneySecurity security; security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().from()); dlg->ui->m_security->setSecurity(security); security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().to()); dlg->ui->m_currency->setSecurity(security); } if (dlg->exec()) { MyMoneyPrice price(dlg->ui->m_security->security().id(), dlg->ui->m_currency->security().id(), dlg->date(), MyMoneyMoney::ONE, QString()); QTreeWidgetItem* p = loadPriceItem(price); d->ui->m_priceList->setCurrentItem(p, true); // If the user cancels the following operation, we delete the new item // and re-select any previously selected one if (slotEditPrice() == Rejected) { delete p; if (item) d->ui->m_priceList->setCurrentItem(item, true); } } } catch (...) { delete dlg; throw; } delete dlg; } int KMyMoneyPriceDlg::slotEditPrice() { Q_D(KMyMoneyPriceDlg); int rc = Rejected; auto item = d->ui->m_priceList->currentItem(); if (item) { MyMoneySecurity from(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().from())); MyMoneySecurity to(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value().to())); signed64 fract = MyMoneyMoney::precToDenom(from.pricePrecision()); QPointer calc = new KCurrencyCalculator(from, to, MyMoneyMoney::ONE, item->data(0, Qt::UserRole).value().rate(to.id()), item->data(0, Qt::UserRole).value().date(), fract, this); calc->setupPriceEditor(); rc = calc->exec(); delete calc; } return rc; } void KMyMoneyPriceDlg::slotDeletePrice() { Q_D(KMyMoneyPriceDlg); QList listItems = d->ui->m_priceList->selectedItems(); if (listItems.count() > 0) { if (KMessageBox::questionYesNo(this, i18np("Do you really want to delete the selected price entry?", "Do you really want to delete the selected price entries?", listItems.count()), i18n("Delete price information"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeletePrice") == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { QList::const_iterator price_it; for (price_it = listItems.constBegin(); price_it != listItems.constEnd(); ++price_it) { MyMoneyFile::instance()->removePrice((*price_it)->data(0, Qt::UserRole).value()); } ft.commit(); } catch (const MyMoneyException &) { qDebug("Cannot delete price"); } } } } void KMyMoneyPriceDlg::slotOnlinePriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == Accepted && dlg) dlg->storePrices(); delete dlg; } void KMyMoneyPriceDlg::slotOpenContextMenu(const QPoint& p) { Q_D(KMyMoneyPriceDlg); auto item = d->ui->m_priceList->itemAt(p); if (item) { d->ui->m_priceList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); emit openContextMenu(item->data(0, Qt::UserRole).value()); } } diff --git a/kmymoney/dialogs/knewaccountdlg.cpp b/kmymoney/dialogs/knewaccountdlg.cpp index 588740aa5..b803798c0 100644 --- a/kmymoney/dialogs/knewaccountdlg.cpp +++ b/kmymoney/dialogs/knewaccountdlg.cpp @@ -1,918 +1,917 @@ /*************************************************************************** knewaccountdlg.cpp ------------------- copyright : (C) 2000 by Michael Edwardes 2004 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #include "knewaccountdlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_knewaccountdlg.h" #include "kmymoneyedit.h" #include "kmymoneydateinput.h" #include #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "kmymoneyglobalsettings.h" #include "kmymoneycurrencyselector.h" #include "knewbankdlg.h" #include "models.h" #include "accountsmodel.h" #include "hierarchyfilterproxymodel.h" #include "mymoneyenums.h" #include "modelenums.h" using namespace eMyMoney; class KNewAccountDlgPrivate { Q_DISABLE_COPY(KNewAccountDlgPrivate) Q_DECLARE_PUBLIC(KNewAccountDlg) public: - - KNewAccountDlgPrivate(KNewAccountDlg *qq) : + explicit KNewAccountDlgPrivate(KNewAccountDlg *qq) : q_ptr(qq), ui(new Ui::KNewAccountDlg) { } ~KNewAccountDlgPrivate() { delete ui; } void init() { Q_Q(KNewAccountDlg); ui->setupUi(q); auto file = MyMoneyFile::instance(); // initialize the m_parentAccount member QVector filterAccountGroup {m_account.accountGroup()}; switch (m_account.accountGroup()) { case Account::Type::Asset: m_parentAccount = file->asset(); break; case Account::Type::Liability: m_parentAccount = file->liability(); break; case Account::Type::Income: m_parentAccount = file->income(); break; case Account::Type::Expense: m_parentAccount = file->expense(); break; case Account::Type::Equity: m_parentAccount = file->equity(); break; default: qDebug("Seems we have an account that hasn't been mapped to the top five"); if (m_categoryEditor) { m_parentAccount = file->income(); filterAccountGroup[0] = Account::Type::Income; } else { m_parentAccount = file->asset(); filterAccountGroup[0] = Account::Type::Asset; } } ui->m_amountGroup->setId(ui->m_grossAmount, 0); ui->m_amountGroup->setId(ui->m_netAmount, 1); // the proxy filter model m_filterProxyModel = new HierarchyFilterProxyModel(q); m_filterProxyModel->setHideClosedAccounts(true); m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode()); m_filterProxyModel->addAccountGroup(filterAccountGroup); m_filterProxyModel->setCurrentAccountId(m_account.id()); auto const model = Models::instance()->accountsModel(); m_filterProxyModel->setSourceModel(model); m_filterProxyModel->setSourceColumns(model->getColumns()); m_filterProxyModel->setDynamicSortFilter(true); ui->m_parentAccounts->setModel(m_filterProxyModel); ui->m_parentAccounts->sortByColumn((int)eAccountsModel::Column::Account, Qt::AscendingOrder); ui->m_subAccountLabel->setText(i18n("Is a sub account")); ui->accountNameEdit->setText(m_account.name()); ui->descriptionEdit->setText(m_account.description()); ui->typeCombo->setEnabled(true); // load the price mode combo ui->m_priceMode->insertItem(i18nc("default price mode", "(default)"), 0); ui->m_priceMode->insertItem(i18n("Price per share"), 1); ui->m_priceMode->insertItem(i18n("Total for all shares"), 2); int priceMode = 0; if (m_account.accountType() == Account::Type::Investment) { ui->m_priceMode->setEnabled(true); if (!m_account.value("priceMode").isEmpty()) priceMode = m_account.value("priceMode").toInt(); } ui->m_priceMode->setCurrentItem(priceMode); bool haveMinBalance = false; bool haveMaxCredit = false; if (!m_account.openingDate().isValid()) { m_account.setOpeningDate(KMyMoneyGlobalSettings::firstFiscalDate()); } ui->m_openingDateEdit->setDate(m_account.openingDate()); handleOpeningBalanceCheckbox(m_account.currencyId()); if (m_categoryEditor) { // get rid of the tabs that are not used for categories int tab = ui->m_tab->indexOf(ui->m_institutionTab); if (tab != -1) ui->m_tab->removeTab(tab); tab = ui->m_tab->indexOf(ui->m_limitsTab); if (tab != -1) ui->m_tab->removeTab(tab); //m_qlistviewParentAccounts->setEnabled(true); ui->accountNoEdit->setEnabled(false); ui->m_institutionBox->hide(); ui->m_qcheckboxNoVat->hide(); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Income), (int)Account::Type::Income); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Expense), (int)Account::Type::Expense); // Hardcoded but acceptable - if above we set the default to income do the same here switch (m_account.accountType()) { case Account::Type::Expense: ui->typeCombo->setCurrentItem(MyMoneyAccount::accountTypeToString(Account::Type::Expense), false); break; case Account::Type::Income: default: ui->typeCombo->setCurrentItem(MyMoneyAccount::accountTypeToString(Account::Type::Income), false); break; } ui->m_currency->setEnabled(true); if (m_isEditing) { ui->typeCombo->setEnabled(false); ui->m_currency->setDisabled(MyMoneyFile::instance()->isReferenced(m_account)); } ui->m_qcheckboxPreferred->hide(); ui->m_qcheckboxTax->setChecked(m_account.value("Tax").toLower() == "yes"); ui->m_costCenterRequiredCheckBox->setChecked(m_account.isCostCenterRequired()); loadVatAccounts(); } else { // get rid of the tabs that are not used for accounts int taxtab = ui->m_tab->indexOf(ui->m_taxTab); if (taxtab != -1) { ui->m_vatCategory->setText(i18n("VAT account")); ui->m_qcheckboxTax->setChecked(m_account.value("Tax") == "Yes"); loadVatAccounts(); } else { ui->m_tab->removeTab(taxtab); } ui->m_costCenterRequiredCheckBox->hide(); switch (m_account.accountType()) { case Account::Type::Savings: case Account::Type::Cash: haveMinBalance = true; break; case Account::Type::Checkings: haveMinBalance = true; haveMaxCredit = true; break; case Account::Type::CreditCard: haveMaxCredit = true; break; default: // no limit available, so we might get rid of the tab int tab = ui->m_tab->indexOf(ui->m_limitsTab); if (tab != -1) ui->m_tab->removeTab(tab); // don't try to hide the widgets we just wiped // in the next step haveMaxCredit = haveMinBalance = true; break; } if (!haveMaxCredit) { ui->m_maxCreditLabel->setEnabled(false); ui->m_maxCreditLabel->hide(); ui->m_maxCreditEarlyEdit->hide(); ui->m_maxCreditAbsoluteEdit->hide(); } if (!haveMinBalance) { ui->m_minBalanceLabel->setEnabled(false); ui->m_minBalanceLabel->hide(); ui->m_minBalanceEarlyEdit->hide(); ui->m_minBalanceAbsoluteEdit->hide(); } QString typeString = MyMoneyAccount::accountTypeToString(m_account.accountType()); if (m_isEditing) { if (m_account.isLiquidAsset()) { ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Checkings), (int)Account::Type::Checkings); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Savings), (int)Account::Type::Savings); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Cash), (int)Account::Type::Cash); } else { ui->typeCombo->addItem(typeString, (int)m_account.accountType()); // Once created, accounts of other account types are not // allowed to be changed. ui->typeCombo->setEnabled(false); } // Once created, a currency cannot be changed if it is referenced. ui->m_currency->setDisabled(MyMoneyFile::instance()->isReferenced(m_account)); } else { ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Checkings), (int)Account::Type::Checkings); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Savings), (int)Account::Type::Savings); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Cash), (int)Account::Type::Cash); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::CreditCard), (int)Account::Type::CreditCard); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Loan), (int)Account::Type::Loan); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Investment), (int)Account::Type::Investment); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Asset), (int)Account::Type::Asset); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Liability), (int)Account::Type::Liability); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Stock), (int)Account::Type::Stock); /* ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::CertificateDep), (int)Account::Type::CertificateDep); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::MoneyMarket), (int)Account::Type::MoneyMarket); ui->typeCombo->addItem(MyMoneyAccount::accountTypeToString(Account::Type::Currency), (int)Account::Type::Currency); */ // Do not create account types that are not supported // by the current engine. if (m_account.accountType() == Account::Type::Unknown || m_account.accountType() == Account::Type::CertificateDep || m_account.accountType() == Account::Type::MoneyMarket || m_account.accountType() == Account::Type::Currency) typeString = MyMoneyAccount::accountTypeToString(Account::Type::Checkings); } ui->typeCombo->setCurrentItem(typeString, false); if (m_account.isInvest()) ui->m_institutionBox->hide(); ui->accountNoEdit->setText(m_account.number()); ui->m_qcheckboxPreferred->setChecked(m_account.value("PreferredAccount") == "Yes"); ui->m_qcheckboxNoVat->setChecked(m_account.value("NoVat") == "Yes"); loadKVP("iban", ui->ibanEdit); loadKVP("minBalanceAbsolute", ui->m_minBalanceAbsoluteEdit); loadKVP("minBalanceEarly", ui->m_minBalanceEarlyEdit); loadKVP("maxCreditAbsolute", ui->m_maxCreditAbsoluteEdit); loadKVP("maxCreditEarly", ui->m_maxCreditEarlyEdit); // reverse the sign for display purposes if (!ui->m_maxCreditAbsoluteEdit->lineedit()->text().isEmpty()) ui->m_maxCreditAbsoluteEdit->setValue(ui->m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE); if (!ui->m_maxCreditEarlyEdit->lineedit()->text().isEmpty()) ui->m_maxCreditEarlyEdit->setValue(ui->m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE); loadKVP("lastNumberUsed", ui->m_lastCheckNumberUsed); if (m_account.isInvest()) { ui->typeCombo->setEnabled(false); ui->m_qcheckboxPreferred->hide(); ui->m_currencyText->hide(); ui->m_currency->hide(); } else { // use the old field and override a possible new value if (!MyMoneyMoney(m_account.value("minimumBalance")).isZero()) { ui->m_minBalanceAbsoluteEdit->setValue(MyMoneyMoney(m_account.value("minimumBalance"))); } } // ui->m_qcheckboxTax->hide(); TODO should only be visible for VAT category/account } ui->m_currency->setSecurity(file->currency(m_account.currencyId())); // Load the institutions // then the accounts QString institutionName; try { if (m_isEditing && !m_account.institutionId().isEmpty()) institutionName = file->institution(m_account.institutionId()).name(); else institutionName.clear(); } catch (const MyMoneyException &e) { qDebug("exception in init for account dialog: %s", qPrintable(e.what())); } if (m_account.isInvest()) ui->m_parentAccounts->setEnabled(false); if (!m_categoryEditor) q->slotLoadInstitutions(institutionName); ui->accountNameEdit->setFocus(); q->connect(ui->buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); q->connect(ui->buttonBox, &QDialogButtonBox::accepted, q, &KNewAccountDlg::okClicked); q->connect(ui->m_parentAccounts->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KNewAccountDlg::slotSelectionChanged); q->connect(ui->m_qbuttonNew, &QAbstractButton::clicked, q, &KNewAccountDlg::slotNewClicked); q->connect(ui->typeCombo, static_cast(&QComboBox::currentIndexChanged), q, &KNewAccountDlg::slotAccountTypeChanged); q->connect(ui->accountNameEdit, &QLineEdit::textChanged, q, &KNewAccountDlg::slotCheckFinished); q->connect(ui->m_vatCategory, &QAbstractButton::toggled, q, &KNewAccountDlg::slotVatChanged); q->connect(ui->m_vatAssignment, &QAbstractButton::toggled, q, &KNewAccountDlg::slotVatAssignmentChanged); q->connect(ui->m_vatCategory, &QAbstractButton::toggled, q, &KNewAccountDlg::slotCheckFinished); q->connect(ui->m_vatAssignment, &QAbstractButton::toggled, q, &KNewAccountDlg::slotCheckFinished); q->connect(ui->m_vatRate, &KMyMoneyEdit::textChanged, q, &KNewAccountDlg::slotCheckFinished); q->connect(ui->m_vatAccount, &KMyMoneySelector::stateChanged, q, &KNewAccountDlg::slotCheckFinished); q->connect(ui->m_currency, static_cast(&QComboBox::activated), q, &KNewAccountDlg::slotCheckCurrency); q->connect(ui->m_minBalanceEarlyEdit, &KMyMoneyEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMinBalanceAbsoluteEdit); q->connect(ui->m_minBalanceAbsoluteEdit, &KMyMoneyEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMinBalanceEarlyEdit); q->connect(ui->m_maxCreditEarlyEdit, &KMyMoneyEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMaxCreditAbsoluteEdit); q->connect(ui->m_maxCreditAbsoluteEdit, &KMyMoneyEdit::valueChanged, q, &KNewAccountDlg::slotAdjustMaxCreditEarlyEdit); q->connect(ui->m_qcomboboxInstitutions, static_cast(&QComboBox::activated), q, &KNewAccountDlg::slotLoadInstitutions); auto parentIndex = m_filterProxyModel->getSelectedParentAccountIndex(); ui->m_parentAccounts->expand(parentIndex); ui->m_parentAccounts->selectionModel()->select(parentIndex, QItemSelectionModel::SelectCurrent); ui->m_parentAccounts->scrollTo(parentIndex, QAbstractItemView::PositionAtTop); ui->m_vatCategory->setChecked(false); ui->m_vatAssignment->setChecked(false); // make sure our account does not have an id and no parent assigned // and certainly no children in case we create a new account if (!m_isEditing) { m_account.clearId(); m_account.setParentAccountId(QString()); m_account.removeAccountIds(); } else { if (!m_account.value("VatRate").isEmpty()) { ui->m_vatCategory->setChecked(true); ui->m_vatRate->setValue(MyMoneyMoney(m_account.value("VatRate"))*MyMoneyMoney(100, 1)); } else { if (!m_account.value("VatAccount").isEmpty()) { QString accId = m_account.value("VatAccount").toLatin1(); try { // make sure account exists MyMoneyFile::instance()->account(accId); ui->m_vatAssignment->setChecked(true); ui->m_vatAccount->setSelected(accId); ui->m_grossAmount->setChecked(true); if (m_account.value("VatAmount") == "Net") ui->m_netAmount->setChecked(true); } catch (const MyMoneyException &) { } } } } q->slotVatChanged(ui->m_vatCategory->isChecked()); q->slotVatAssignmentChanged(ui->m_vatAssignment->isChecked()); q->slotCheckFinished(); auto requiredFields = new KMandatoryFieldGroup(q); requiredFields->setOkButton(ui->buttonBox->button(QDialogButtonBox::Ok)); // button to be enabled when all fields present requiredFields->add(ui->accountNameEdit); } void loadKVP(const QString& key, KMyMoneyEdit* widget) { if (!widget) return; if (m_account.value(key).isEmpty()) { widget->clearText(); } else { widget->setValue(MyMoneyMoney(m_account.value(key))); } } void loadKVP(const QString& key, KLineEdit* widget) { if (!widget) return; widget->setText(m_account.value(key)); } void storeKVP(const QString& key, const QString& text, const QString& value) { if (text.isEmpty()) m_account.deletePair(key); else m_account.setValue(key, value); } void storeKVP(const QString& key, QCheckBox* widget) { if (widget) { if(widget->isChecked()) { m_account.setValue(key, "Yes");; } else { m_account.deletePair(key); } } } void storeKVP(const QString& key, KMyMoneyEdit* widget) { storeKVP(key, widget->lineedit()->text(), widget->text()); } void storeKVP(const QString& key, KLineEdit* widget) { storeKVP(key, widget->text(), widget->text()); } void loadVatAccounts() { QList list; MyMoneyFile::instance()->accountList(list); QList::Iterator it; QStringList loadListExpense; QStringList loadListIncome; QStringList loadListAsset; QStringList loadListLiability; for (it = list.begin(); it != list.end(); ++it) { if (!(*it).value("VatRate").isEmpty()) { if ((*it).accountType() == Account::Type::Expense) loadListExpense += (*it).id(); else if ((*it).accountType() == Account::Type::Income) loadListIncome += (*it).id(); else if ((*it).accountType() == Account::Type::Asset) loadListAsset += (*it).id(); else if ((*it).accountType() == Account::Type::Liability) loadListLiability += (*it).id(); } } AccountSet vatSet; if (!loadListAsset.isEmpty()) vatSet.load(ui->m_vatAccount, i18n("Asset"), loadListAsset, true); if (!loadListLiability.isEmpty()) vatSet.load(ui->m_vatAccount, i18n("Liability"), loadListLiability, false); if (!loadListIncome.isEmpty()) vatSet.load(ui->m_vatAccount, i18n("Income"), loadListIncome, false); if (!loadListExpense.isEmpty()) vatSet.load(ui->m_vatAccount, i18n("Expense"), loadListExpense, false); } void adjustEditWidgets(KMyMoneyEdit* dst, KMyMoneyEdit* src, char mode, int corr) { MyMoneyMoney factor(corr, 1); if (m_account.accountGroup() == Account::Type::Asset) factor = -factor; switch (mode) { case '<': if (src->value()*factor < dst->value()*factor) dst->setValue(src->value()); break; case '>': if (src->value()*factor > dst->value()*factor) dst->setValue(src->value()); break; } } void handleOpeningBalanceCheckbox(const QString ¤cyId) { if (m_account.accountType() == Account::Type::Equity) { // check if there is another opening balance account with the same currency bool isOtherOpenBalancingAccount = false; QList list; MyMoneyFile::instance()->accountList(list); QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { if (it->id() == m_account.id() || currencyId != it->currencyId() || it->accountType() != Account::Type::Equity) continue; if (it->value("OpeningBalanceAccount") == "Yes") { isOtherOpenBalancingAccount = true; break; } } if (!isOtherOpenBalancingAccount) { bool isOpenBalancingAccount = m_account.value("OpeningBalanceAccount") == "Yes"; ui->m_qcheckboxOpeningBalance->setChecked(isOpenBalancingAccount); if (isOpenBalancingAccount) { // let only allow state change if no transactions are assigned to this account bool hasTransactions = MyMoneyFile::instance()->transactionCount(m_account.id()) != 0; ui->m_qcheckboxOpeningBalance->setEnabled(!hasTransactions); if (hasTransactions) ui->m_qcheckboxOpeningBalance->setToolTip(i18n("Option has been disabled because there are transactions assigned to this account")); } } else { ui->m_qcheckboxOpeningBalance->setChecked(false); ui->m_qcheckboxOpeningBalance->setEnabled(false); ui->m_qcheckboxOpeningBalance->setToolTip(i18n("Option has been disabled because there is another account flagged to be an opening balance account for this currency")); } } else { ui->m_qcheckboxOpeningBalance->setVisible(false); } } KNewAccountDlg *q_ptr; Ui::KNewAccountDlg *ui; MyMoneyAccount m_account; MyMoneyAccount m_parentAccount; HierarchyFilterProxyModel *m_filterProxyModel; bool m_categoryEditor; bool m_isEditing; }; KNewAccountDlg::KNewAccountDlg(const MyMoneyAccount& account, bool isEditing, bool categoryEditor, QWidget *parent, const QString& title) : QDialog(parent), d_ptr(new KNewAccountDlgPrivate(this)) { Q_D(KNewAccountDlg); d->m_account = account; d->m_categoryEditor = categoryEditor; d->m_isEditing = isEditing; if (!title.isEmpty()) setWindowTitle(title); d->init(); } MyMoneyMoney KNewAccountDlg::openingBalance() const { Q_D(const KNewAccountDlg); return d->ui->m_openingBalanceEdit->value(); } void KNewAccountDlg::setOpeningBalance(const MyMoneyMoney& balance) { Q_D(KNewAccountDlg); d->ui->m_openingBalanceEdit->setValue(balance); } void KNewAccountDlg::setOpeningBalanceShown(bool shown) { Q_D(KNewAccountDlg); d->ui->m_openingBalanceLabel->setVisible(shown); d->ui->m_openingBalanceEdit->setVisible(shown); } void KNewAccountDlg::setOpeningDateShown(bool shown) { Q_D(KNewAccountDlg); d->ui->m_openingDateLabel->setVisible(shown); d->ui->m_openingDateEdit->setVisible(shown); } void KNewAccountDlg::okClicked() { Q_D(KNewAccountDlg); auto file = MyMoneyFile::instance(); QString accountNameText = d->ui->accountNameEdit->text(); if (accountNameText.isEmpty()) { KMessageBox::error(this, i18n("You have not specified a name.\nPlease fill in this field.")); d->ui->accountNameEdit->setFocus(); return; } MyMoneyAccount parent = parentAccount(); if (parent.name().length() == 0) { KMessageBox::error(this, i18n("Please select a parent account.")); return; } if (!d->m_categoryEditor) { QString institutionNameText = d->ui->m_qcomboboxInstitutions->currentText(); if (institutionNameText != i18n("(No Institution)")) { try { auto file = MyMoneyFile::instance(); QList list = file->institutionList(); QList::ConstIterator institutionIterator; for (institutionIterator = list.constBegin(); institutionIterator != list.constEnd(); ++institutionIterator) { if ((*institutionIterator).name() == institutionNameText) d->m_account.setInstitutionId((*institutionIterator).id()); } } catch (const MyMoneyException &e) { qDebug("Exception in account institution set: %s", qPrintable(e.what())); } } else { d->m_account.setInstitutionId(QString()); } } d->m_account.setName(accountNameText); d->m_account.setNumber(d->ui->accountNoEdit->text()); d->storeKVP("iban", d->ui->ibanEdit); d->storeKVP("minBalanceAbsolute", d->ui->m_minBalanceAbsoluteEdit); d->storeKVP("minBalanceEarly", d->ui->m_minBalanceEarlyEdit); // the figures for credit line with reversed sign if (!d->ui->m_maxCreditAbsoluteEdit->lineedit()->text().isEmpty()) d->ui->m_maxCreditAbsoluteEdit->setValue(d->ui->m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE); if (!d->ui->m_maxCreditEarlyEdit->lineedit()->text().isEmpty()) d->ui->m_maxCreditEarlyEdit->setValue(d->ui->m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE); d->storeKVP("maxCreditAbsolute", d->ui->m_maxCreditAbsoluteEdit); d->storeKVP("maxCreditEarly", d->ui->m_maxCreditEarlyEdit); if (!d->ui->m_maxCreditAbsoluteEdit->lineedit()->text().isEmpty()) d->ui->m_maxCreditAbsoluteEdit->setValue(d->ui->m_maxCreditAbsoluteEdit->value()*MyMoneyMoney::MINUS_ONE); if (!d->ui->m_maxCreditEarlyEdit->lineedit()->text().isEmpty()) d->ui->m_maxCreditEarlyEdit->setValue(d->ui->m_maxCreditEarlyEdit->value()*MyMoneyMoney::MINUS_ONE); d->storeKVP("lastNumberUsed", d->ui->m_lastCheckNumberUsed); // delete a previous version of the minimumbalance information d->storeKVP("minimumBalance", QString(), QString()); Account::Type acctype; if (!d->m_categoryEditor) { acctype = static_cast(d->ui->typeCombo->currentData().toInt()); // If it's a loan, check if the parent is asset or liability. In // case of asset, we change the account type to be AssetLoan if (acctype == Account::Type::Loan && parent.accountGroup() == Account::Type::Asset) acctype = Account::Type::AssetLoan; } else { acctype = parent.accountGroup(); QString newName; if (!MyMoneyFile::instance()->isStandardAccount(parent.id())) { newName = MyMoneyFile::instance()->accountToCategory(parent.id()) + MyMoneyFile::AccountSeperator; } newName += accountNameText; if (!file->categoryToAccount(newName, acctype).isEmpty() && (file->categoryToAccount(newName, acctype) != d->m_account.id())) { KMessageBox::error(this, QString("") + i18n("A category named %1 already exists. You cannot create a second category with the same name.", newName) + QString("")); return; } } d->m_account.setAccountType(acctype); d->m_account.setDescription(d->ui->descriptionEdit->toPlainText()); d->m_account.setOpeningDate(d->ui->m_openingDateEdit->date()); if (!d->m_categoryEditor) { d->m_account.setCurrencyId(d->ui->m_currency->security().id()); d->storeKVP("PreferredAccount", d->ui->m_qcheckboxPreferred); d->storeKVP("NoVat", d->ui->m_qcheckboxNoVat); if (d->ui->m_minBalanceAbsoluteEdit->isVisible()) { d->m_account.setValue("minimumBalance", d->ui->m_minBalanceAbsoluteEdit->value().toString()); } } else { if (KMyMoneyGlobalSettings::hideUnusedCategory() && !d->m_isEditing) { KMessageBox::information(this, i18n("You have selected to suppress the display of unused categories in the KMyMoney configuration dialog. The category you just created will therefore only be shown if it is used. Otherwise, it will be hidden in the accounts/categories view."), i18n("Hidden categories"), "NewHiddenCategory"); } d->m_account.setCostCenterRequired(d->ui->m_costCenterRequiredCheckBox->isChecked()); } d->storeKVP("Tax", d->ui->m_qcheckboxTax); if (d->ui->m_qcheckboxOpeningBalance->isChecked()) d->m_account.setValue("OpeningBalanceAccount", "Yes"); else d->m_account.deletePair("OpeningBalanceAccount"); d->m_account.deletePair("VatAccount"); d->m_account.deletePair("VatAmount"); d->m_account.deletePair("VatRate"); if (d->ui->m_vatCategory->isChecked()) { d->m_account.setValue("VatRate", (d->ui->m_vatRate->value().abs() / MyMoneyMoney(100, 1)).toString()); } else { if (d->ui->m_vatAssignment->isChecked() && !d->ui->m_vatAccount->selectedItems().isEmpty()) { d->m_account.setValue("VatAccount", d->ui->m_vatAccount->selectedItems().first()); if (d->ui->m_netAmount->isChecked()) d->m_account.setValue("VatAmount", "Net"); } } accept(); } MyMoneyAccount KNewAccountDlg::account() { Q_D(KNewAccountDlg); // assign the right currency to the account d->m_account.setCurrencyId(d->ui->m_currency->security().id()); // and the price mode switch (d->ui->m_priceMode->currentItem()) { case 0: d->m_account.deletePair("priceMode"); break; case 1: case 2: d->m_account.setValue("priceMode", QString("%1").arg(d->ui->m_priceMode->currentItem())); break; } return d->m_account; } MyMoneyAccount KNewAccountDlg::parentAccount() const { Q_D(const KNewAccountDlg); return d->m_parentAccount; } void KNewAccountDlg::slotSelectionChanged(const QItemSelection ¤t, const QItemSelection &previous) { Q_UNUSED(previous) Q_D(KNewAccountDlg); if (!current.indexes().empty()) { QVariant account = d->ui->m_parentAccounts->model()->data(current.indexes().front(), (int)eAccountsModel::Role::Account); if (account.isValid()) { d->m_parentAccount = account.value(); d->ui->m_subAccountLabel->setText(i18n("Is a sub account of %1", d->m_parentAccount.name())); } } } void KNewAccountDlg::slotLoadInstitutions(const QString& name) { Q_D(KNewAccountDlg); d->ui->m_qcomboboxInstitutions->clear(); QString bic; // Are we forcing the user to use institutions? d->ui->m_qcomboboxInstitutions->addItem(i18n("(No Institution)")); d->ui->m_bicValue->setText(" "); d->ui->ibanEdit->setEnabled(false); d->ui->accountNoEdit->setEnabled(false); try { auto file = MyMoneyFile::instance(); QList list = file->institutionList(); QList::ConstIterator institutionIterator; for (institutionIterator = list.constBegin(); institutionIterator != list.constEnd(); ++institutionIterator) { if ((*institutionIterator).name() == name) { d->ui->ibanEdit->setEnabled(true); d->ui->accountNoEdit->setEnabled(true); d->ui->m_bicValue->setText((*institutionIterator).value("bic")); } d->ui->m_qcomboboxInstitutions->addItem((*institutionIterator).name()); } d->ui->m_qcomboboxInstitutions->setCurrentItem(name, false); } catch (const MyMoneyException &e) { qDebug("Exception in institution load: %s", qPrintable(e.what())); } } void KNewAccountDlg::slotNewClicked() { MyMoneyInstitution institution; QPointer dlg = new KNewBankDlg(institution, this); if (dlg->exec()) { MyMoneyFileTransaction ft; try { auto file = MyMoneyFile::instance(); institution = dlg->institution(); file->addInstitution(institution); ft.commit(); slotLoadInstitutions(institution.name()); } catch (const MyMoneyException &) { KMessageBox::information(this, i18n("Cannot add institution")); } } delete dlg; } void KNewAccountDlg::slotAccountTypeChanged(int index) { Q_D(KNewAccountDlg); Account::Type oldType; auto type = d->ui->typeCombo->itemData(index).value(); try { oldType = d->m_account.accountType(); if (oldType != type) { d->m_account.setAccountType(type); // update the account group displayed in the accounts hierarchy d->m_filterProxyModel->clear(); d->m_filterProxyModel->addAccountGroup(QVector {d->m_account.accountGroup()}); } } catch (const MyMoneyException &) { qWarning("Unexpected exception in KNewAccountDlg::slotAccountTypeChanged()"); } } void KNewAccountDlg::slotCheckFinished() { Q_D(KNewAccountDlg); auto showButton = true; if (d->ui->accountNameEdit->text().length() == 0) { showButton = false; } if (d->ui->m_vatCategory->isChecked() && d->ui->m_vatRate->value() <= MyMoneyMoney()) { showButton = false; } else { if (d->ui->m_vatAssignment->isChecked() && d->ui->m_vatAccount->selectedItems().isEmpty()) showButton = false; } d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(showButton); } void KNewAccountDlg::slotVatChanged(bool state) { Q_D(KNewAccountDlg); if (state) { d->ui->m_vatCategoryFrame->show(); d->ui->m_vatAssignmentFrame->hide(); } else { d->ui->m_vatCategoryFrame->hide(); if (!d->m_account.isAssetLiability()) { d->ui->m_vatAssignmentFrame->show(); } } } void KNewAccountDlg::slotVatAssignmentChanged(bool state) { Q_D(KNewAccountDlg); d->ui->m_vatAccount->setEnabled(state); d->ui->m_amountGroupBox->setEnabled(state); } void KNewAccountDlg::slotAdjustMinBalanceAbsoluteEdit(const QString&) { Q_D(KNewAccountDlg); d->adjustEditWidgets(d->ui->m_minBalanceAbsoluteEdit, d->ui->m_minBalanceEarlyEdit, '<', -1); } void KNewAccountDlg::slotAdjustMinBalanceEarlyEdit(const QString&) { Q_D(KNewAccountDlg); d->adjustEditWidgets(d->ui->m_minBalanceEarlyEdit, d->ui->m_minBalanceAbsoluteEdit, '>', -1); } void KNewAccountDlg::slotAdjustMaxCreditAbsoluteEdit(const QString&) { Q_D(KNewAccountDlg); d->adjustEditWidgets(d->ui->m_maxCreditAbsoluteEdit, d->ui->m_maxCreditEarlyEdit, '>', 1); } void KNewAccountDlg::slotAdjustMaxCreditEarlyEdit(const QString&) { Q_D(KNewAccountDlg); d->adjustEditWidgets(d->ui->m_maxCreditEarlyEdit, d->ui->m_maxCreditAbsoluteEdit, '<', 1); } void KNewAccountDlg::slotCheckCurrency(int index) { Q_D(KNewAccountDlg); Q_UNUSED(index) d->handleOpeningBalanceCheckbox(d->ui->m_currency->security().id()); } void KNewAccountDlg::addTab(QWidget* w, const QString& name) { Q_D(KNewAccountDlg); if (w) { w->setParent(d->ui->m_tab); d->ui->m_tab->addTab(w, name); } } diff --git a/kmymoney/dialogs/konlinetransferform.h b/kmymoney/dialogs/konlinetransferform.h index 2ed09ea90..4abfcdbce 100644 --- a/kmymoney/dialogs/konlinetransferform.h +++ b/kmymoney/dialogs/konlinetransferform.h @@ -1,137 +1,137 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * (C) 2017 by Ł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 KONLINETRANSFERFORM_H #define KONLINETRANSFERFORM_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/onlinejob.h" #include "mymoney/onlinejobadministration.h" class IonlineJobEdit; class KMandatoryFieldGroup; namespace Ui { class kOnlineTransferForm; } /** * @brief The kOnlineTransferForm class * * @todo Disable Send/Enque button if no task is shown. * @todo If this dialog is shown a second time without setting a onlineJob it, it shows the previous content. * Fix this by creating the IonlineJobEdit widgets on demand and destroying them afterwards. */ class kOnlineTransferForm : public QDialog { Q_OBJECT public: - kOnlineTransferForm(QWidget *parent = nullptr); + explicit kOnlineTransferForm(QWidget *parent = nullptr); virtual ~kOnlineTransferForm(); signals: /** @brief The user wants this job to be saved */ void acceptedForSave(onlineJob); /** @brief User wants to send the onlineJob directly */ void acceptedForSend(onlineJob); public slots: virtual void accept(); virtual void reject(); /** @brief sets the current origin account */ virtual void setCurrentAccount(const QString& accountId); /** * @brief Sets an onlineTransfer to edit * * @return true if there is widget which supports editing this onlineJob */ virtual bool setOnlineJob(const onlineJob); void duplicateCurrentJob(); private slots: /** @brief Slot for account selection box */ void accountChanged(); /** * @brief Slot to change job type * @param index of KComboBox (== index of selected widget in m_onlineJobEditWidgets) */ void convertCurrentJob(const int& index); /** @brief Slot for send button */ void sendJob(); /** * @brief Load a plugin */ void loadOnlineJobEditPlugin(const onlineJobAdministration::onlineJobEditOffer& plugin); /** @{ */ /** * @brief Activates the onlineJobEdit widget */ bool showEditWidget(const QString& onlineTaskName); void showEditWidget(IonlineJobEdit* widget); /** @} */ /** * @brief Shows warning if checkEditWidget() == false */ void checkNotSupportedWidget(); void setJobReadOnly(const bool&); private: /** * @brief returns the currently edited onlineJob * Can be a null job */ onlineJob activeOnlineJob() const; Ui::kOnlineTransferForm* ui; QList m_onlineJobEditWidgets; KMandatoryFieldGroup* m_requiredFields; QAction* m_duplicateJob; /** * @brief Checks if widget can edit any task the selected account supports */ bool checkEditWidget(IonlineJobEdit* widget); /** * @brief Checks current widget * @see checkEditWidget( IonlineJobEdit* widget ) */ bool checkEditWidget(); void editWidgetChanged(); }; #endif // KONLINETRANSFERFORM_H diff --git a/kmymoney/dialogs/kpayeereassigndlg.h b/kmymoney/dialogs/kpayeereassigndlg.h index d1044121e..211b14f67 100644 --- a/kmymoney/dialogs/kpayeereassigndlg.h +++ b/kmymoney/dialogs/kpayeereassigndlg.h @@ -1,85 +1,85 @@ /*************************************************************************** kpayeereassigndlg.cpp ------------------- copyright : (C) 2005 by Andreas Nicolai (C) 2007 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KPAYEEREASSIGNDLG_H #define KPAYEEREASSIGNDLG_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes /** * Implementation of the dialog that lets the user select a payee in order * to re-assign transactions (for instance, if payees are deleted). */ class MyMoneyPayee; class KPayeeReassignDlgPrivate; class KPayeeReassignDlg : public QDialog { Q_OBJECT Q_DISABLE_COPY(KPayeeReassignDlg) public: /** Change behavior based on type of operation */ enum OperationType { TypeMerge = 0, TypeDelete, TypeCount, }; /** Default constructor */ - KPayeeReassignDlg(OperationType type, QWidget* parent = nullptr); + explicit KPayeeReassignDlg(OperationType type, QWidget* parent = nullptr); /** Destructor */ ~KPayeeReassignDlg(); /** * This function sets up the dialog, lets the user select a payee and returns * the id of the selected payee in the payeeslist. * * @param payeeslist reference to QList of MyMoneyPayee objects to be contained in the list * * @return Returns the id of the selected payee in the list or QString() if * the dialog was aborted. QString() is also returned if the payeeslist is empty. */ QString show(const QList& payeeslist); /** * Returns true, if the names of the payees to be deleted should be copied * to the selected payee's match list. */ bool addToMatchList() const; protected: void accept() override; private: KPayeeReassignDlgPrivate * const d_ptr; Q_DECLARE_PRIVATE(KPayeeReassignDlg) }; #endif // KPAYEEREASSIGNDLG_H diff --git a/kmymoney/dialogs/ksplittransactiondlg.cpp b/kmymoney/dialogs/ksplittransactiondlg.cpp index 6785e3e34..91747d973 100644 --- a/kmymoney/dialogs/ksplittransactiondlg.cpp +++ b/kmymoney/dialogs/ksplittransactiondlg.cpp @@ -1,568 +1,568 @@ /*************************************************************************** ksplittransactiondlg.cpp - description ------------------- begin : Thu Jan 10 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Ł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. * * * ***************************************************************************/ #include "ksplittransactiondlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_ksplittransactiondlg.h" #include "ui_ksplitcorrectiondlg.h" #include "mymoneyfile.h" #include "kmymoneysplittable.h" #include "mymoneymoney.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "icons/icons.h" using namespace Icons; KSplitCorrectionDlg::KSplitCorrectionDlg(QWidget *parent) : QDialog(parent), ui(new Ui::KSplitCorrectionDlg) { ui->setupUi(this); } KSplitCorrectionDlg::~KSplitCorrectionDlg() { delete ui; } class KSplitTransactionDlgPrivate { Q_DISABLE_COPY(KSplitTransactionDlgPrivate) Q_DECLARE_PUBLIC(KSplitTransactionDlg) public: - KSplitTransactionDlgPrivate(KSplitTransactionDlg *qq) : + explicit KSplitTransactionDlgPrivate(KSplitTransactionDlg *qq) : q_ptr(qq), ui(new Ui::KSplitTransactionDlg) { } ~KSplitTransactionDlgPrivate() { delete ui; } void init(const MyMoneyTransaction& t, const QMap& priceInfo) { Q_Q(KSplitTransactionDlg); ui->setupUi(q); q->setModal(true); auto okButton = ui->buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); auto user1Button = new QPushButton; ui->buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole); auto user2Button = new QPushButton; ui->buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole); auto user3Button = new QPushButton; ui->buttonBox->addButton(user3Button, QDialogButtonBox::ActionRole); //set custom buttons //clearAll button user1Button->setText(i18n("Clear &All")); user1Button->setToolTip(i18n("Clear all splits")); user1Button->setWhatsThis(i18n("Use this to clear all splits of this transaction")); user1Button->setIcon(QIcon::fromTheme(g_Icons[Icon::EditClear])); //clearZero button user2Button->setText(i18n("Clear &Zero")); user2Button->setToolTip(i18n("Removes all splits that have a value of zero")); user2Button->setIcon(QIcon::fromTheme(g_Icons[Icon::EditClear])); //merge button user3Button->setText(i18n("&Merge")); user3Button->setToolTip(i18n("Merges splits with the same category to one split")); user3Button->setWhatsThis(i18n("In case you have multiple split entries to the same category and you like to keep them as a single split")); // make finish the default ui->buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); // setup the focus ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocusPolicy(Qt::NoFocus); okButton->setFocusPolicy(Qt::NoFocus); user1Button->setFocusPolicy(Qt::NoFocus); // q->connect signals with slots q->connect(ui->transactionsTable, &KMyMoneySplitTable::transactionChanged, q, &KSplitTransactionDlg::slotSetTransaction); q->connect(ui->transactionsTable, &KMyMoneySplitTable::createCategory, q, &KSplitTransactionDlg::slotCreateCategory); q->connect(ui->transactionsTable, &KMyMoneySplitTable::objectCreation, q, &KSplitTransactionDlg::objectCreation); q->connect(ui->transactionsTable, &KMyMoneySplitTable::returnPressed, q, &KSplitTransactionDlg::accept); q->connect(ui->transactionsTable, &KMyMoneySplitTable::escapePressed, q, &KSplitTransactionDlg::reject); q->connect(ui->transactionsTable, &KMyMoneySplitTable::editStarted, q, &KSplitTransactionDlg::slotEditStarted); q->connect(ui->transactionsTable, &KMyMoneySplitTable::editFinished, q, &KSplitTransactionDlg::slotUpdateButtons); q->connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, q, &KSplitTransactionDlg::reject); q->connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KSplitTransactionDlg::accept); q->connect(user1Button, &QAbstractButton::clicked, q, &KSplitTransactionDlg::slotClearAllSplits); q->connect(user3Button, &QAbstractButton::clicked, q, &KSplitTransactionDlg::slotMergeSplits); q->connect(user2Button, &QAbstractButton::clicked, q, &KSplitTransactionDlg::slotClearUnusedSplits); // setup the precision try { auto currency = MyMoneyFile::instance()->currency(t.commodity()); m_precision = MyMoneyMoney::denomToPrec(m_account.fraction(currency)); } catch (const MyMoneyException &) { } q->slotSetTransaction(t); // pass on those vars ui->transactionsTable->setup(priceInfo, m_precision); QSize size(q->width(), q->height()); KConfigGroup grp = KSharedConfig::openConfig()->group("SplitTransactionEditor"); size = grp.readEntry("Geometry", size); size.setHeight(size.height() - 1); q->resize(size.expandedTo(q->minimumSizeHint())); // Trick: it seems, that the initial sizing of the dialog does // not work correctly. At least, the columns do not get displayed // correct. Reason: the return value of ui->transactionsTable->visibleWidth() // is incorrect. If the widget is visible, resizing works correctly. // So, we let the dialog show up and resize it then. It's not really // clean, but the only way I got the damned thing working. QTimer::singleShot(10, q, SLOT(initSize())); } /** * This method updates the display of the sums below the register */ void updateSums() { Q_Q(KSplitTransactionDlg); MyMoneyMoney splits(q->splitsValue()); if (m_amountValid == false) { m_split.setValue(-splits); m_transaction.modifySplit(m_split); } ui->splitSum->setText("" + splits.formatMoney(QString(), m_precision) + ' '); ui->splitUnassigned->setText("" + q->diffAmount().formatMoney(QString(), m_precision) + ' '); ui->transactionAmount->setText("" + (-m_split.value()).formatMoney(QString(), m_precision) + ' '); } KSplitTransactionDlg *q_ptr; Ui::KSplitTransactionDlg *ui; QDialogButtonBox *m_buttonBox; /** * This member keeps a copy of the current selected transaction */ MyMoneyTransaction m_transaction; /** * This member keeps a copy of the currently selected account */ MyMoneyAccount m_account; /** * This member keeps a copy of the currently selected split */ MyMoneySplit m_split; /** * This member keeps the precision for the values */ int m_precision; /** * flag that shows that the amount specified in the constructor * should be used as fix value (true) or if it can be changed (false) */ bool m_amountValid; /** * This member keeps track if the current transaction is of type * deposit (true) or withdrawal (false). */ bool m_isDeposit; /** * This member keeps the amount that will be assigned to all the * splits that are marked 'will be calculated'. */ MyMoneyMoney m_calculatedValue; }; KSplitTransactionDlg::KSplitTransactionDlg(const MyMoneyTransaction& t, const MyMoneySplit& s, const MyMoneyAccount& acc, const bool amountValid, const bool deposit, const MyMoneyMoney& calculatedValue, const QMap& priceInfo, QWidget* parent) : QDialog(parent), d_ptr(new KSplitTransactionDlgPrivate(this)) { Q_D(KSplitTransactionDlg); d->ui->buttonBox = nullptr; d->m_account = acc; d->m_split = s; d->m_precision = 2; d->m_amountValid = amountValid; d->m_isDeposit = deposit; d->m_calculatedValue = calculatedValue; d->init(t, priceInfo); } KSplitTransactionDlg::~KSplitTransactionDlg() { Q_D(KSplitTransactionDlg); auto grp = KSharedConfig::openConfig()->group("SplitTransactionEditor"); grp.writeEntry("Geometry", size()); delete d; } int KSplitTransactionDlg::exec() { Q_D(KSplitTransactionDlg); // for deposits, we invert the sign of all splits. // don't forget to revert when we're done ;-) if (d->m_isDeposit) { for (auto i = 0; i < d->m_transaction.splits().count(); ++i) { MyMoneySplit split = d->m_transaction.splits()[i]; split.setValue(-split.value()); split.setShares(-split.shares()); d->m_transaction.modifySplit(split); } } int rc; do { d->ui->transactionsTable->setFocus(); // initialize the display d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); d->updateSums(); rc = QDialog::exec(); if (rc == Accepted) { if (!diffAmount().isZero()) { QPointer corrDlg = new KSplitCorrectionDlg(this); connect(corrDlg->ui->buttonBox, &QDialogButtonBox::accepted, corrDlg.data(), &QDialog::accept); connect(corrDlg->ui->buttonBox, &QDialogButtonBox::rejected, corrDlg.data(), &QDialog::reject); corrDlg->ui->buttonGroup->setId(corrDlg->ui->continueBtn, 0); corrDlg->ui->buttonGroup->setId(corrDlg->ui->changeBtn, 1); corrDlg->ui->buttonGroup->setId(corrDlg->ui->distributeBtn, 2); corrDlg->ui->buttonGroup->setId(corrDlg->ui->leaveBtn, 3); MyMoneySplit split = d->m_transaction.splits()[0]; QString total = (-split.value()).formatMoney(QString(), d->m_precision); QString sums = splitsValue().formatMoney(QString(), d->m_precision); QString diff = diffAmount().formatMoney(QString(), d->m_precision); // now modify the text items of the dialog to contain the correct values QString q = i18n("The total amount of this transaction is %1 while " "the sum of the splits is %2. The remaining %3 are " "unassigned.", total, sums, diff); corrDlg->ui->explanation->setText(q); q = i18n("Change &total amount of transaction to %1.", sums); corrDlg->ui->changeBtn->setText(q); q = i18n("&Distribute difference of %1 among all splits.", diff); corrDlg->ui->distributeBtn->setText(q); // FIXME remove the following line once distribution among // all splits is implemented corrDlg->ui->distributeBtn->hide(); // if we have only two splits left, we don't allow leaving sth. unassigned. if (d->m_transaction.splitCount() < 3) { q = i18n("&Leave total amount of transaction at %1.", total); } else { q = i18n("&Leave %1 unassigned.", diff); } corrDlg->ui->leaveBtn->setText(q); if ((rc = corrDlg->exec()) == Accepted) { switch (corrDlg->ui->buttonGroup->checkedId()) { case 0: // continue to edit rc = Rejected; break; case 1: // modify total split.setValue(-splitsValue()); split.setShares(-splitsValue()); d->m_transaction.modifySplit(split); break; case 2: // distribute difference qDebug("distribution of difference not yet supported in KSplitTransactionDlg::slotFinishClicked()"); break; case 3: // leave unassigned break; } } delete corrDlg; } } else break; } while (rc != Accepted); // for deposits, we inverted the sign of all splits. // now we revert it back, so that things are left correct if (d->m_isDeposit) { for (auto i = 0; i < d->m_transaction.splits().count(); ++i) { auto split = d->m_transaction.splits()[i]; split.setValue(-split.value()); split.setShares(-split.shares()); d->m_transaction.modifySplit(split); } } return rc; } void KSplitTransactionDlg::initSize() { QDialog::resize(width(), height() + 1); } void KSplitTransactionDlg::accept() { Q_D(KSplitTransactionDlg); d->ui->transactionsTable->slotCancelEdit(); QDialog::accept(); } void KSplitTransactionDlg::reject() { Q_D(KSplitTransactionDlg); // cancel any edit activity in the split register d->ui->transactionsTable->slotCancelEdit(); QDialog::reject(); } void KSplitTransactionDlg::slotClearAllSplits() { Q_D(KSplitTransactionDlg); int answer; answer = KMessageBox::warningContinueCancel(this, i18n("You are about to delete all splits of this transaction. " "Do you really want to continue?"), i18n("KMyMoney")); if (answer == KMessageBox::Continue) { d->ui->transactionsTable->slotCancelEdit(); QList list = d->ui->transactionsTable->getSplits(d->m_transaction); QList::ConstIterator it; // clear all but the one referencing the account for (it = list.constBegin(); it != list.constEnd(); ++it) { d->m_transaction.removeSplit(*it); } d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); slotSetTransaction(d->m_transaction); } } void KSplitTransactionDlg::slotClearUnusedSplits() { Q_D(KSplitTransactionDlg); QList list = d->ui->transactionsTable->getSplits(d->m_transaction); QList::ConstIterator it; try { // remove all splits that don't have a value assigned for (it = list.constBegin(); it != list.constEnd(); ++it) { if ((*it).shares().isZero()) { d->m_transaction.removeSplit(*it); } } d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); slotSetTransaction(d->m_transaction); } catch (const MyMoneyException &) { } } void KSplitTransactionDlg::slotMergeSplits() { Q_D(KSplitTransactionDlg); QList list = d->ui->transactionsTable->getSplits(d->m_transaction); QList::ConstIterator it; try { // collect all splits, merge them if needed and remove from transaction QList splits; for (it = list.constBegin(); it != list.constEnd(); ++it) { QList::iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { if ((*it_s).accountId() == (*it).accountId() && (*it_s).memo().isEmpty() && (*it).memo().isEmpty()) break; } if (it_s != splits.end()) { (*it_s).setShares((*it).shares() + (*it_s).shares()); (*it_s).setValue((*it).value() + (*it_s).value()); } else { splits << *it; } d->m_transaction.removeSplit(*it); } // now add them back to the transaction QList::iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { (*it_s).clearId(); d->m_transaction.addSplit(*it_s); } d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); slotSetTransaction(d->m_transaction); } catch (const MyMoneyException &) { } } void KSplitTransactionDlg::slotSetTransaction(const MyMoneyTransaction& t) { Q_D(KSplitTransactionDlg); d->m_transaction = t; slotUpdateButtons(); d->updateSums(); } void KSplitTransactionDlg::slotUpdateButtons() { Q_D(KSplitTransactionDlg); QList list = d->ui->transactionsTable->getSplits(d->m_transaction); // check if we can merge splits or not, have zero splits or not QMap splits; bool haveZeroSplit = false; for (QList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { splits[(*it).accountId()]++; if (((*it).id() != d->m_split.id()) && ((*it).shares().isZero())) haveZeroSplit = true; } QMap::const_iterator it_s; for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { if ((*it_s) > 1) break; } d->ui->buttonBox->buttons().at(4)->setEnabled(it_s != splits.constEnd()); d->ui->buttonBox->buttons().at(3)->setEnabled(haveZeroSplit); } void KSplitTransactionDlg::slotEditStarted() { Q_D(KSplitTransactionDlg); d->ui->buttonBox->buttons().at(4)->setEnabled(false); d->ui->buttonBox->buttons().at(3)->setEnabled(false); } MyMoneyMoney KSplitTransactionDlg::splitsValue() { Q_D(KSplitTransactionDlg); MyMoneyMoney splitsValue(d->m_calculatedValue); QList list = d->ui->transactionsTable->getSplits(d->m_transaction); QList::ConstIterator it; // calculate the current sum of all split parts for (it = list.constBegin(); it != list.constEnd(); ++it) { if ((*it).value() != MyMoneyMoney::autoCalc) splitsValue += (*it).value(); } return splitsValue; } MyMoneyTransaction KSplitTransactionDlg::transaction() const { Q_D(const KSplitTransactionDlg); return d->m_transaction; } MyMoneyMoney KSplitTransactionDlg::diffAmount() { Q_D(KSplitTransactionDlg); MyMoneyMoney diff; // if there is an amount specified in the transaction, we need to calculate the // difference, otherwise we display the difference as 0 and display the same sum. if (d->m_amountValid) { MyMoneySplit split = d->m_transaction.splits()[0]; diff = -(splitsValue() + split.value()); } return diff; } void KSplitTransactionDlg::slotCreateCategory(const QString& name, QString& id) { Q_D(KSplitTransactionDlg); MyMoneyAccount acc, parent; acc.setName(name); if (d->m_isDeposit) parent = MyMoneyFile::instance()->income(); else parent = MyMoneyFile::instance()->expense(); // TODO extract possible first part of a hierarchy and check if it is one // of our top categories. If so, remove it and select the parent // according to this information. emit createCategory(acc, parent); // return id id = acc.id(); } diff --git a/kmymoney/dialogs/transactioneditor_p.h b/kmymoney/dialogs/transactioneditor_p.h index 83c2b4e2f..08c3e09b0 100644 --- a/kmymoney/dialogs/transactioneditor_p.h +++ b/kmymoney/dialogs/transactioneditor_p.h @@ -1,126 +1,126 @@ /*************************************************************************** transactioneditor_p.h ---------- begin : Wed Jun 07 2006 copyright : (C) 2006 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef TRANSACTIONEDITOR_P_H #define TRANSACTIONEDITOR_P_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneylineedit.h" #include "kmymoneyutils.h" #include "mymoneyaccount.h" #include "mymoneyenums.h" #include "mymoneyfile.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "register.h" #include "registeritem.h" #include "selectedtransactions.h" #include "transactioneditor.h" #include "qwidgetcontainer.h" #include "widgetenums.h" class MyMoneyMoney; class TransactionEditorContainer; namespace KMyMoneyRegister { class Transaction; } class TransactionEditorPrivate { Q_DISABLE_COPY(TransactionEditorPrivate) Q_DECLARE_PUBLIC(TransactionEditor) public: - TransactionEditorPrivate(TransactionEditor *qq) : - q_ptr(qq) + explicit TransactionEditorPrivate(TransactionEditor *qq) : + q_ptr(qq) { } ~TransactionEditorPrivate() { } void init() { m_paymentMethod = eMyMoney::Schedule::PaymentType::Any; m_regForm = 0; m_item = 0; m_initialAction = eWidgets::eRegister::Action::None; m_openEditSplits = false; m_memoChanged = false; } /** * If a new or an edited transaction has a valid number, keep it with the account */ void keepNewNumber(const MyMoneyTransaction& tr) { Q_Q(TransactionEditor); // verify that new number, possibly containing alpha, is valid auto txn = tr; auto file = MyMoneyFile::instance(); if (!txn.splits().isEmpty()) { QString number = txn.splits().first().number(); if (KMyMoneyUtils::numericPart(number) > 0) { // numeric is valid auto numberEdit = dynamic_cast(q->haveWidget("number")); if (numberEdit) { numberEdit->loadText(number); MyMoneySplit split = txn.splits().first(); split.setNumber(number); txn.modifySplit(split); m_account.setValue("lastNumberUsed", number); file->modifyAccount(m_account); } } } } TransactionEditor *q_ptr; QString m_scheduleInfo; eMyMoney::Schedule::PaymentType m_paymentMethod; QString m_memoText; QList m_splits; KMyMoneyRegister::SelectedTransactions m_transactions; QList m_finalEditWidgets; TransactionEditorContainer* m_regForm; KMyMoneyRegister::Transaction* m_item; KMyMoneyRegister::QWidgetContainer m_editWidgets; MyMoneyAccount m_account; MyMoneyTransaction m_transaction; MyMoneySplit m_split; QDate m_lastPostDate; QMap m_priceInfo; eWidgets::eRegister::Action m_initialAction; bool m_openEditSplits; bool m_memoChanged; }; #endif // KMERGETRANSACTIONSDLG_H diff --git a/kmymoney/misc/charvalidator.h b/kmymoney/misc/charvalidator.h index d98a89083..542f33428 100644 --- a/kmymoney/misc/charvalidator.h +++ b/kmymoney/misc/charvalidator.h @@ -1,38 +1,38 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2015 Christian Dávid * * 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 CHARVALIDATOR_H #define CHARVALIDATOR_H #include class charValidator : public QValidator { Q_OBJECT public: - charValidator(QObject* parent = 0, const QString& characters = QString()); + explicit charValidator(QObject* parent = 0, const QString& characters = QString()); virtual QValidator::State validate(QString& , int&) const; void setAllowedCharacters(const QString&); private: QString m_allowedCharacters; }; #endif // CHARVALIDATOR_H diff --git a/kmymoney/misc/webconnect.h b/kmymoney/misc/webconnect.h index 2da7f43a6..ed1fbcb90 100644 --- a/kmymoney/misc/webconnect.h +++ b/kmymoney/misc/webconnect.h @@ -1,55 +1,55 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2017 Thomas Baumgart * * 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 WEBCONNECT_H #define WEBCONNECT_H #include #include Q_DECLARE_LOGGING_CATEGORY(WebConnectLog) class WebConnect : public QObject { Q_OBJECT public: - WebConnect(QObject* parent); + explicit WebConnect(QObject* parent); virtual ~WebConnect(); bool isClient() const; public Q_SLOTS: void loadFile(const QUrl& url); private Q_SLOTS: void serverConnected(); void serverDisconnected(); void clientConnected(); void clientDisconnected(); void dataAvailable(); Q_SIGNALS: void gotUrl(const QUrl& url); private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; }; #endif // WEBCONNECT_H diff --git a/kmymoney/models/accountsproxymodel.h b/kmymoney/models/accountsproxymodel.h index 6e24691ef..32eb811c0 100644 --- a/kmymoney/models/accountsproxymodel.h +++ b/kmymoney/models/accountsproxymodel.h @@ -1,107 +1,107 @@ /*************************************************************************** * Copyright 2010 Cristian Onet onet.cristian@gmail.com * * Copyright 2017 Łukasz Wojniłowicz lukasz.wojnilowicz@gmail.com * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 ACCOUNTSPROXYMODEL_H #define ACCOUNTSPROXYMODEL_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes /** * A proxy model to provide various sorting and filtering operations for @ref AccountsModel. * * Here is an example of how to use this class in combination with the @ref AccountsModel. * (in the example @a widget is a pointer to a model/view widget): * * @code * AccountsFilterProxyModel *filterModel = new AccountsFilterProxyModel(widget); * filterModel->addAccountGroup(eMyMoney::Account::Type::Asset); * filterModel->addAccountGroup(eMyMoney::Account::Type::Liability); * filterModel->setSourceModel(Models::instance()->accountsModel()); * filterModel->sort(0); * * widget->setModel(filterModel); * @endcode * * @see AccountsModel * * @author Cristian Onet 2010 * */ namespace eMyMoney { namespace Account {enum class Type;} } namespace eAccountsModel { enum class Column; } class AccountsProxyModel : public KRecursiveFilterProxyModel { Q_OBJECT public: - AccountsProxyModel(QObject *parent = nullptr); + explicit AccountsProxyModel(QObject *parent = nullptr); ~AccountsProxyModel(); void addAccountType(eMyMoney::Account::Type type); void addAccountGroup(const QVector &groups); void removeAccountType(eMyMoney::Account::Type type); void clear(); void setHideClosedAccounts(bool hideClosedAccounts); bool hideClosedAccounts() const; void setHideEquityAccounts(bool hideEquityAccounts); bool hideEquityAccounts() const; void setHideUnusedIncomeExpenseAccounts(bool hideUnusedIncomeExpenseAccounts); bool hideUnusedIncomeExpenseAccounts() const; int visibleItems(bool includeBaseAccounts = false) const; void setSourceColumns(QList *columns); QList *m_mdlColumns; protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool acceptSourceItem(const QModelIndex &source) const; bool filterAcceptsRowOrChildRows(int source_row, const QModelIndex &source_parent) const; int visibleItems(const QModelIndex& index) const; signals: void unusedIncomeExpenseAccountHidden() const; private: class Private; Private* const d; }; #endif diff --git a/kmymoney/models/ledgermodel.h b/kmymoney/models/ledgermodel.h index bbbe0851c..02086fdcd 100644 --- a/kmymoney/models/ledgermodel.h +++ b/kmymoney/models/ledgermodel.h @@ -1,112 +1,112 @@ /*************************************************************************** ledgermodel.cpp ------------------- begin : Sat Aug 8 2015 copyright : (C) 2015 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef LEDGERMODEL_H #define LEDGERMODEL_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class MyMoneyObject; class MyMoneySchedule; class MyMoneySplit; class MyMoneyTransaction; class LedgerTransaction; namespace eMyMoney { namespace File { enum class Object; } } class LedgerModelPrivate; class LedgerModel : public QAbstractTableModel { Q_OBJECT public: - LedgerModel(QObject* parent = nullptr); + explicit LedgerModel(QObject* parent = nullptr); virtual ~LedgerModel(); int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex& index) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; /** * clears all objects currently in the model */ void unload(); /** * Adds the transaction items in the @a list to the model */ void addTransactions(const QList >& list); /** * Adds a single transaction @a t to the model */ void addTransaction(const LedgerTransaction& t); /** * Adds a single split based on its transactionSplitId */ void addTransaction(const QString& transactionSplitId); /** * Adds the schedule items in the @a list to the model */ void addSchedules(const QList< MyMoneySchedule >& list, int previewPeriod); /** * Loads the model with data from the engine */ void load(); /** * This method extracts the transaction id from a combined * transactionSplitId and returns it. In case the @a transactionSplitId does * not resembles a transactionSplitId an empty string is returned. */ QString transactionIdFromTransactionSplitId(const QString& transactionSplitId) const; public Q_SLOTS: protected Q_SLOTS: void removeTransaction(eMyMoney::File::Object objType, const QString& id); void addTransaction (eMyMoney::File::Object objType, const MyMoneyObject * const obj); void modifyTransaction(eMyMoney::File::Object objType, const MyMoneyObject * const obj); void removeSchedule (eMyMoney::File::Object objType, const QString& id); void addSchedule (eMyMoney::File::Object objType, const MyMoneyObject * const obj); void modifySchedule (eMyMoney::File::Object objType, const MyMoneyObject * const obj); private: Q_DISABLE_COPY(LedgerModel) Q_DECLARE_PRIVATE(LedgerModel) const QScopedPointer d_ptr; }; #endif // LEDGERMODEL_H diff --git a/kmymoney/models/modeltest.h b/kmymoney/models/modeltest.h index e15d4f9bd..d384dfe98 100644 --- a/kmymoney/models/modeltest.h +++ b/kmymoney/models/modeltest.h @@ -1,94 +1,94 @@ /**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef MODELTEST_H #define MODELTEST_H #include #include #include class ModelTest : public QObject { Q_OBJECT public: - ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); + explicit ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); private Q_SLOTS: void nonDestructiveBasicTest(); void rowCount(); void columnCount(); void hasIndex(); void index(); void parent(); void data(); protected Q_SLOTS: void runAllTests(); void layoutAboutToBeChanged(); void layoutChanged(); void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); void rowsInserted( const QModelIndex & parent, int start, int end ); void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); void rowsRemoved( const QModelIndex & parent, int start, int end ); private: void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); QAbstractItemModel *model; struct Changing { QModelIndex parent; int oldSize; QVariant last; QVariant next; }; QStack insert; QStack remove; bool fetchingMore; QList changing; }; #endif diff --git a/kmymoney/models/onlinebankingaccountsfilterproxymodel.h b/kmymoney/models/onlinebankingaccountsfilterproxymodel.h index 1c606feab..46806615c 100644 --- a/kmymoney/models/onlinebankingaccountsfilterproxymodel.h +++ b/kmymoney/models/onlinebankingaccountsfilterproxymodel.h @@ -1,46 +1,46 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 ONLINEBANKINGACCOUNTSFILTERPROXYMODEL_H #define ONLINEBANKINGACCOUNTSFILTERPROXYMODEL_H #include class OnlineBankingAccountsFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - OnlineBankingAccountsFilterProxyModel(QObject* parent = 0); + explicit OnlineBankingAccountsFilterProxyModel(QObject* parent = 0); /** * @brief Makes accounts which do not support any onlineJob non-selectable */ virtual Qt::ItemFlags flags(const QModelIndex& index) const; protected: virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; private: /** * @brief Has parent at least one visible child? */ bool filterAcceptsParent(const QModelIndex& index) const; }; #endif // ONLINEBANKINGACCOUNTSFILTERPROXYMODEL_H diff --git a/kmymoney/models/payeeidentifiercontainermodel.h b/kmymoney/models/payeeidentifiercontainermodel.h index 4850d8d69..0a5797409 100644 --- a/kmymoney/models/payeeidentifiercontainermodel.h +++ b/kmymoney/models/payeeidentifiercontainermodel.h @@ -1,96 +1,96 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 PAYEEIDENTIFIERCONTAINERMODEL_H #define PAYEEIDENTIFIERCONTAINERMODEL_H #include #include #include "mymoney/payeeidentifiermodel.h" #include "mymoney/mymoneypayeeidentifiercontainer.h" #include "payeeidentifier/payeeidentifier.h" /** * @brief Model for MyMoneyPayeeIdentifierContainer * * Changes the user does have initernal effect only. * * @see payeeIdentifierModel */ class MyMoneyPayeeIdentifierContainer; class payeeIdentifier; class payeeIdentifierContainerModel : public QAbstractListModel { Q_OBJECT public: /** * @brief Roles for this model * * They are equal to payeeIdentifierModel::roles */ enum roles { payeeIdentifierType = payeeIdentifierModel::payeeIdentifierType, /**< type of payeeIdentifier */ payeeIdentifier = payeeIdentifierModel::payeeIdentifier /**< actual payeeIdentifier */ }; - payeeIdentifierContainerModel(QObject* parent = 0); + explicit payeeIdentifierContainerModel(QObject* parent = 0); virtual QVariant data(const QModelIndex& index, int role) const; /** * This model only supports to edit payeeIdentifier role with a QVariant of type * payeeIdentifier. */ virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); virtual Qt::ItemFlags flags(const QModelIndex& index) const; virtual int rowCount(const QModelIndex& parent) const; virtual bool insertRows(int row, int count, const QModelIndex& parent); virtual bool removeRows(int row, int count, const QModelIndex& parent); /** * @brief Set source of data * * This makes the model editable. */ void setSource(MyMoneyPayeeIdentifierContainer data); /** @brief Get stored data */ QList< ::payeeIdentifier > identifiers() const; public slots: /** * @brief Removes all data from the model * * The model is not editable afterwards. */ void closeSource(); private: /** @internal * The use of a shared pointer makes this future prof. Because using identifier() causes * some unnecessary work. */ QSharedPointer m_data; }; #endif // PAYEEIDENTIFIERCONTAINERMODEL_H diff --git a/kmymoney/mymoney/mymoneycontact.h b/kmymoney/mymoney/mymoneycontact.h index 9183d7fa9..388c5b628 100644 --- a/kmymoney/mymoney/mymoneycontact.h +++ b/kmymoney/mymoney/mymoneycontact.h @@ -1,80 +1,80 @@ /* * Copyright 2014 Cristian Oneț * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 MYMONEYCONTACT_H #define MYMONEYCONTACT_H #include #include #include #include "kmm_mymoney_export.h" /** * POD containig contact data, these fields are retrived based on an email address. */ struct ContactData { QString email; QString phoneNumber; QString street; QString locality; QString country; QString region; QString postalCode; }; Q_DECLARE_METATYPE(ContactData); class KJob; /** * This class can be used to retrieve contact fields from the address book based on an email * address. It's hides the KDE PIM libraries dependency so it can be made optional. */ class KMM_MYMONEY_EXPORT MyMoneyContact : public QObject { Q_OBJECT public: - MyMoneyContact(QObject *parent); + explicit MyMoneyContact(QObject *parent); /** * Properties of the default identity (the current user). */ bool ownerExists() const; QString ownerEmail() const; QString ownerFullName() const; public slots: /** * Use this slot to start retrieving contact data for an email. */ void fetchContact(const QString &email); signals: /** * This signal is emitted when the contact data was retrieved. */ void contactFetched(const ContactData &identity); private slots: void searchContactResult(KJob *job); }; #endif // MYMONEYCONTACT_H diff --git a/kmymoney/mymoney/mymoneyfile.cpp b/kmymoney/mymoney/mymoneyfile.cpp index f24566ae4..4cc6cbee7 100644 --- a/kmymoney/mymoney/mymoneyfile.cpp +++ b/kmymoney/mymoney/mymoneyfile.cpp @@ -1,3751 +1,3751 @@ /*************************************************************************** mymoneyfile.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2002, 2007-2011 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #include "mymoneyfile.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "imymoneystorage.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 "mymoneyobjectcontainer.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.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::AccountSeperator = 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, bool reload = true) { if (!id.isEmpty()) m_notificationList[id] = reload; } void addCacheNotification(const QString& id, const QDate& date, bool reload = true) { if (!id.isEmpty()) { m_notificationList[id] = reload; 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_notificationList.clear(); m_balanceNotifyList.clear(); } /** * This method is used to clear all * objects mentioned in m_notificationList from the cache. */ void notify() { QMap::ConstIterator it = m_notificationList.constBegin(); while (it != m_notificationList.constEnd()) { if (*it) m_cache.refresh(it.key()); else m_cache.clear(it.key()); ++it; } 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("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("No transaction started for %1").arg(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 */ IMyMoneyStorage *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. */ MyMoneyObjectContainer m_cache; MyMoneyPriceList m_priceCache; MyMoneyBalanceCache m_balanceCache; /** * This member keeps a list of ids to notify after a single * operation is completed. The boolean is used as follows * during processing of the list: * * false - don't reload the object immediately * true - reload the object immediately */ CacheNotifyList m_notificationList; /** * 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(IMyMoneyStorage *storage) : d(new Private) { attachStorage(storage); } void MyMoneyFile::attachStorage(IMyMoneyStorage* const storage) { if (d->m_storage != 0) throw MYMONEYEXCEPTION("Storage already attached"); if (storage == 0) throw MYMONEYEXCEPTION("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_cache.clear(storage); d->m_priceCache.clear(); preloadCache(); // notify application about new data availability emit beginChangeNotification(); emit dataChanged(); emit endChangeNotification(); } void MyMoneyFile::detachStorage(IMyMoneyStorage* const /* storage */) { d->m_balanceCache.clear(); d->m_cache.clear(); d->m_priceCache.clear(); d->m_storage = 0; } IMyMoneyStorage* 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("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 bool changed = d->m_storage->commitTransaction(); d->m_inTransaction = false; // 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 QList::const_iterator it = d->m_changeSet.constBegin(); while (it != d->m_changeSet.constEnd()) { if ((*it).notificationMode() == File::Mode::Remove) { emit objectRemoved((*it).objectType(), (*it).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((*it).id()); } else { const MyMoneyObject * obj = 0; MyMoneyTransaction tr; switch((*it).objectType()) { case File::Object::Transaction: tr = transaction((*it).id()); obj = &tr; break; default: obj = d->m_cache.object((*it).id()); break; } if (obj) { if ((*it).notificationMode() == File::Mode::Add) { emit objectAdded((*it).objectType(), obj); } else { emit objectModified((*it).objectType(), obj); } } } ++it; } // 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. foreach (const QString& id, d->m_balanceChangedSet) { // 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); const MyMoneyAccount& acc = d->m_cache.account(id); emit balanceChanged(acc); } d->m_balanceChangedSet.clear(); // now notify about the remaining value changes foreach (const QString& id, d->m_valueChangedSet) { const MyMoneyAccount& acc = d->m_cache.account(id); emit valueChanged(acc); } 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; preloadCache(); 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("Not a new institution"); d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addInstitution(institution); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Add, institution); } void MyMoneyFile::modifyInstitution(const MyMoneyInstitution& institution) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifyInstitution(institution); d->addCacheNotification(institution.id()); 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; QList::ConstIterator it_s; for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { // the following line will throw an exception if the // account does not exist MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot store split with no account assigned"); if (isStandardAccount((*it_s).accountId())) throw MYMONEYEXCEPTION("Cannot store split referencing standard account"); if (acc.isLoan() && ((*it_s).action() == MyMoneySplit::ActionTransfer)) loanAccountAffected = true; } // change transfer splits between asset/liability and loan accounts // into amortization splits if (loanAccountAffected) { QList list = transaction.splits(); for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if ((*it_s).action() == MyMoneySplit::ActionTransfer) { MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId()); if (acc.isAssetLiability()) { MyMoneySplit s = (*it_s); s.setAction(MyMoneySplit::ActionAmortization); 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 for (it_s = tr.splits().constBegin(); it_s != tr.splits().constEnd(); ++it_s) { d->addCacheNotification((*it_s).accountId(), tr.postDate()); d->addCacheNotification((*it_s).payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).tagList()); ?? } // 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 for (it_s = tCopy.splits().constBegin(); it_s != tCopy.splits().constEnd(); ++it_s) { d->addCacheNotification((*it_s).accountId(), tCopy.postDate()); d->addCacheNotification((*it_s).payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).tagList()); ?? } d->m_changeSet += MyMoneyNotification(File::Mode::Modify, transaction); } void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount account(_account); MyMoneyAccount 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("Unable to modify the standard account groups"); } if (account.accountType() != acc.accountType() && !account.isLiquidAsset() && !acc.isLiquidAsset()) throw MYMONEYEXCEPTION("Unable to change account type"); // clear all changed objects from cache MyMoneyNotifier notifier(d); // 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->addCacheNotification(acc.institutionId()); d->addCacheNotification(account.institutionId()); } d->m_storage->modifyAccount(account); d->addCacheNotification(account.id()); 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("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("Unable to reparent Stock to non-investment account"); if (parent.accountType() == Account::Type::Investment && !acc.isInvest()) throw MYMONEYEXCEPTION("Unable to reparent non-stock to investment account"); // clear all changed objects from cache MyMoneyNotifier notifier(d); // keep a notification of the current parent MyMoneyAccount curParent = account(acc.parentAccountId()); d->addCacheNotification(curParent.id()); d->m_storage->reparentAccount(acc, parent); // and also keep one for the account itself and the new parent d->addCacheNotification(acc.id()); d->addCacheNotification(parent.id()); 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("Unable to reparent to different account type"); } const MyMoneyInstitution& MyMoneyFile::institution(const QString& id) const { return d->m_cache.institution(id); } const MyMoneyAccount& MyMoneyFile::account(const QString& id) const { return d->m_cache.account(id); } const MyMoneyAccount& MyMoneyFile::subAccountByName(const MyMoneyAccount& acc, const QString& name) const { static MyMoneyAccount nullAccount; QList::const_iterator it_a; for (it_a = acc.accountList().constBegin(); it_a != acc.accountList().constEnd(); ++it_a) { const MyMoneyAccount& sacc = account(*it_a); if (sacc.name() == name) return sacc; } return nullAccount; } const MyMoneyAccount& MyMoneyFile::accountByName(const QString& name) const { return d->m_cache.accountByName(name); } 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()); QList::ConstIterator it_s; // scan the splits again to update notification list for (it_s = tr.splits().constBegin(); it_s != tr.splits().constEnd(); ++it_s) { MyMoneyAccount acc = account((*it_s).accountId()); if (acc.isClosed()) throw MYMONEYEXCEPTION(i18n("Cannot remove transaction that references a closed account.")); d->addCacheNotification((*it_s).accountId(), tr.postDate()); d->addCacheNotification((*it_s).payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).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); MyMoneyNotifier notifier(d); MyMoneyAccount acc = account(id); d->m_storage->setAccountName(id, name); d->addCacheNotification(id); 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("Unable to remove the standard account groups"); if (hasActiveSplits(account.id())) { throw MYMONEYEXCEPTION("Unable to remove account with active splits"); } // clear all changed objects from cache MyMoneyNotifier notifier(d); // collect all sub-ordinate accounts for notification foreach (const QString& id, acc.accountList()) { d->addCacheNotification(id); const MyMoneyAccount& acc = MyMoneyFile::account(id); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } // don't forget the parent and a possible institution d->addCacheNotification(parent.id()); d->addCacheNotification(account.institutionId()); 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->addCacheNotification(acc.id(), false); d->m_cache.clear(acc.id()); 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("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("One or more accounts cannot be removed"); } } // process all accounts in the list and test if they have transactions assigned for (QStringList::ConstIterator it = account_list.constBegin(); it != account_list.constEnd(); ++it) { MyMoneyAccount a = d->m_storage->account(*it); //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(*it); } // make sure to remove the item from the cache d->m_cache.clear(a.id()); removeAccount(a); } } bool MyMoneyFile::hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level) { if (level > 100) throw MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::hasOnlyUnusedAccounts]!"); // process all accounts in the list and test if they have transactions assigned for (QStringList::ConstIterator it = account_list.constBegin(); it != account_list.constEnd(); ++it) { if (transactionCount(*it) != 0) return false; // the current account has a transaction assigned if (!hasOnlyUnusedAccounts(account(*it).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); // clear all changed objects from cache MyMoneyNotifier notifier(d); QList::ConstIterator it_a; MyMoneyInstitution inst = MyMoneyFile::institution(institution.id()); bool blocked = signalsBlocked(); blockSignals(true); for (it_a = inst.accountList().constBegin(); it_a != inst.accountList().constEnd(); ++it_a) { MyMoneyAccount acc = account(*it_a); acc.setInstitutionId(QString()); modifyAccount(acc); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } blockSignals(blocked); d->m_storage->removeInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, institution); d->addCacheNotification(institution.id(), false); } 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::AccountSeperator)) != -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").toLatin1()); 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", qPrintable(e.what())); throw MYMONEYEXCEPTION(e.what()); } } 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("Account has no name"); if (account.id().length() != 0) throw MYMONEYEXCEPTION("New account must have no id"); if (account.accountList().count() != 0) throw MYMONEYEXCEPTION("New account must have no sub-accounts"); if (!account.parentAccountId().isEmpty()) throw MYMONEYEXCEPTION("New account must have no parent-id"); if (account.accountType() == Account::Type::Unknown) throw MYMONEYEXCEPTION("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 MyMoneyAccount 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("Stock account cannot be parent account"); if (account.isInvest() && parent.accountType() != Account::Type::Investment) throw MYMONEYEXCEPTION("Stock account must have investment account as parent "); if (!account.isInvest() && parent.accountType() == Account::Type::Investment) throw MYMONEYEXCEPTION("Investment account can only have stock accounts as children"); // clear all changed objects from cache MyMoneyNotifier notifier(d); // 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); d->addCacheNotification(institution.id()); } // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadAccount(account); d->addCacheNotification(parent.id()); } 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; } const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) { if (!security.isCurrency()) throw MYMONEYEXCEPTION("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; } } const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const { return openingBalanceAccount_internal(security); } const MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const { if (!security.isCurrency()) throw MYMONEYEXCEPTION("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("No opening balance account for %1").arg(security.tradingSymbol())); } return acc; } const 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("Unable to add transaction with id set"); if (!transaction.postDate().isValid()) throw MYMONEYEXCEPTION("Unable to add transaction with invalid postdate"); // now check the splits bool loanAccountAffected = false; QList::ConstIterator it_s; for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot add split with no account assigned"); if (acc.isLoan()) loanAccountAffected = true; if (isStandardAccount((*it_s).accountId())) throw MYMONEYEXCEPTION("Cannot add split referencing standard account"); } // change transfer splits between asset/liability and loan accounts // into amortization splits if (loanAccountAffected) { QList list = transaction.splits(); for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if ((*it_s).action() == MyMoneySplit::ActionTransfer) { MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId()); if (acc.isAssetLiability()) { MyMoneySplit s = (*it_s); s.setAction(MyMoneySplit::ActionAmortization); 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 for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { d->addCacheNotification((*it_s).accountId(), transaction.postDate()); d->addCacheNotification((*it_s).payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification((*it_s).tagList()); ?? } d->m_changeSet += MyMoneyNotification(File::Mode::Add, transaction); } const MyMoneyTransaction MyMoneyFile::transaction(const QString& id) const { d->checkStorage(); return d->m_storage->transaction(id); } const 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addPayee(payee); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadPayee(payee); d->m_changeSet += MyMoneyNotification(File::Mode::Add, payee); } const MyMoneyPayee& MyMoneyFile::payee(const QString& id) const { return d->m_cache.payee(id); } const MyMoneyPayee& MyMoneyFile::payeeByName(const QString& name) const { d->checkStorage(); return d->m_cache.payee(d->m_storage->payeeByName(name).id()); } void MyMoneyFile::modifyPayee(const MyMoneyPayee& payee) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->addCacheNotification(payee.id()); 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 // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removePayee(payee); d->addCacheNotification(payee.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, payee); } void MyMoneyFile::addTag(MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addTag(tag); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadTag(tag); d->m_changeSet += MyMoneyNotification(File::Mode::Add, tag); } const MyMoneyTag& MyMoneyFile::tag(const QString& id) const { return d->m_cache.tag(id); } const MyMoneyTag& MyMoneyFile::tagByName(const QString& name) const { d->checkStorage(); return d->m_cache.tag(d->m_storage->tagByName(name).id()); } void MyMoneyFile::modifyTag(const MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->addCacheNotification(tag.id()); 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 // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeTag(tag); d->addCacheNotification(tag.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, tag); } void MyMoneyFile::accountList(QList& list, const QStringList& idlist, const bool recursive) const { if (idlist.isEmpty()) { d->m_cache.account(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_cache.account(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); } } } } } } void MyMoneyFile::institutionList(QList& list) const { d->m_cache.institution(list); } const QList MyMoneyFile::institutionList() const { QList list; institutionList(list); return list; } // general get functions const 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); 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()); } } const MyMoneyAccount& MyMoneyFile::liability() const { d->checkStorage(); return d->m_cache.account(STD_ACC_LIABILITY); } const MyMoneyAccount& MyMoneyFile::asset() const { d->checkStorage(); return d->m_cache.account(STD_ACC_ASSET); } const MyMoneyAccount& MyMoneyFile::expense() const { d->checkStorage(); return d->m_cache.account(STD_ACC_EXPENSE); } const MyMoneyAccount& MyMoneyFile::income() const { d->checkStorage(); return d->m_cache.account(STD_ACC_INCOME); } const MyMoneyAccount& MyMoneyFile::equity() const { d->checkStorage(); return d->m_cache.account(STD_ACC_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()); } const QMap MyMoneyFile::transactionCountMap() const { d->checkStorage(); return d->m_storage->transactionCountMap(); } unsigned int MyMoneyFile::institutionCount() const { d->checkStorage(); return d->m_storage->institutionCount(); } const 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; } const MyMoneyMoney MyMoneyFile::balance(const QString& id) const { return balance(id, QDate()); } const 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; } const MyMoneyMoney MyMoneyFile::totalBalance(const QString& id, const QDate& date) const { d->checkStorage(); return d->m_storage->totalBalance(id, date); } const 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(%ld) %s", qPrintable(e.file()), e.line(), qPrintable(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); } const QList MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const { QList list; transactionList(list, filter); return list; } const QList MyMoneyFile::payeeList() const { QList list; d->m_cache.payee(list); return list; } const QList MyMoneyFile::tagList() const { QList list; d->m_cache.tag(list); return list; } QString MyMoneyFile::accountToCategory(const QString& accountId, bool includeStandardAccounts) const { MyMoneyAccount acc; QString rc; if (!accountId.isEmpty()) { acc = account(accountId); do { if (!rc.isEmpty()) rc = AccountSeperator + 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(AccountSeperator, 0, -2); } QString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const QString& category) const { MyMoneyAccount nextBase; QString level, remainder; level = category.section(AccountSeperator, 0, 0); remainder = category.section(AccountSeperator, 1); QStringList list = base.accountList(); QStringList::ConstIterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { nextBase = account(*it_a); 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->setValue(key, val); } void MyMoneyFile::deletePair(const QString& key) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->deletePair(key); } void MyMoneyFile::addSchedule(MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); MyMoneyTransaction transaction = sched.transaction(); QList::ConstIterator it_s; for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot add split with no account assigned"); if (isStandardAccount((*it_s).accountId())) throw MYMONEYEXCEPTION("Cannot add split referencing standard account"); } // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addSchedule(sched); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadSchedule(sched); d->m_changeSet += MyMoneyNotification(File::Mode::Add, sched); } void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); MyMoneyTransaction transaction = sched.transaction(); QList::ConstIterator it_s; for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot store split with no account assigned"); if (isStandardAccount((*it_s).accountId())) throw MYMONEYEXCEPTION("Cannot store split referencing standard account"); } // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifySchedule(sched); d->addCacheNotification(sched.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, sched); } void MyMoneyFile::removeSchedule(const MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeSchedule(sched); d->addCacheNotification(sched.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, sched); } const MyMoneySchedule MyMoneyFile::schedule(const QString& id) const { return d->m_cache.schedule(id); } const 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); } const QList MyMoneyFile::scheduleList( const QString& accountId) const { return scheduleList(accountId, Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); } const QList MyMoneyFile::scheduleList() const { return scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); } const 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); // 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()); d->addCacheNotification((*it_a).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("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); d->addCacheNotification((*it_a).id()); } 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); d->addCacheNotification((*it_a).id()); } 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 QList tList; MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); d->m_storage->transactionList(tList, filter); // Generate the list of interest accounts for (it_t = tList.begin(); it_t != tList.end(); ++it_t) { const MyMoneyTransaction& t = (*it_t); QList::const_iterator it_s; for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { if ((*it_s).action() == MyMoneySplit::ActionInterest) interestAccounts[(*it_s).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 (it_t = tList.begin(); it_t != tList.end(); ++it_t) { MyMoneyTransaction t = (*it_t); QList splits = t.splits(); QList::const_iterator it_s; bool tChanged = false; QDate accountOpeningDate; QStringList accountList; for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { bool sChanged = false; MyMoneySplit s = (*it_s); if (payeeConversionMap.find((*it_s).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 MyMoneyAccount& 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(), (*it_s).id(), (*it_s).accountId()); ++unfixedCount; } // make sure the interest splits are marked correct as such if (interestAccounts.find(s.accountId()) != interestAccounts.end() && s.action() != MyMoneySplit::ActionInterest) { s.setAction(MyMoneySplit::ActionInterest); 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; for (it_t = t.splits().constBegin(); it_t != t.splits().constEnd(); ++it_t) { if (((*it_t).action() != "Buy") && ((*it_t).action() != "Reinvest")) { continue; } QString id = (*it_t).accountId(); MyMoneyAccount acc = this->account(id); MyMoneySecurity sec = this->security(acc.currencyId()); MyMoneyPrice price(acc.currencyId(), sec.tradingCurrency(), t.postDate(), (*it_t).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(); QList splits = t.splits(); bool tChanged = false; QList::const_iterator it_s; for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { MyMoneySplit s = (*it_s); bool sChanged = false; if (payeeConversionMap.find((*it_s).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 (!(*it_s).value().isZero() && (*it_s).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 (!(*it_s).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 MyMoneyAccount& 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(), (*it_s).id(), (*it_s).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 pList; QStringList::Iterator it_p; (*it_r).payees(pList); bool rChanged = false; for (it_p = pList.begin(); it_p != pList.end(); ++it_p) { if (payeeConversionMap.find(*it_p) != payeeConversionMap.end()) { rc << i18n(" * Payee id updated in report '%1'.", (*it_r).name()); ++problemCount; r.removeReference(*it_p); r.addPayee(payeeConversionMap[*it_p]); 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 foreing 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 investments/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 investment '%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 investment 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("Invalid base category"); QStringList subAccounts = name.split(AccountSeperator); 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 += (AccountSeperator + *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), qPrintable(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 arbitraty 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 parent; if (newAccount.parentAccountId().isEmpty()) { if (!value.isNegative() && value2.isNegative()) parent = income(); else parent = expense(); } else { parent = 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, parent, brokerage, MyMoneyMoney()); accountId = newAccount.id(); } } return accountId; } const QList MyMoneyFile::scheduleListEx(int scheduleTypes, int scheduleOcurrences, int schedulePaymentTypes, QDate startDate, const QStringList& accounts) const { d->checkStorage(); return d->m_storage->scheduleListEx(scheduleTypes, scheduleOcurrences, schedulePaymentTypes, startDate, accounts); } void MyMoneyFile::addSecurity(MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addSecurity(security); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadSecurity(security); d->m_changeSet += MyMoneyNotification(File::Mode::Add, security); } void MyMoneyFile::modifySecurity(const MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifySecurity(security); d->addCacheNotification(security.id()); 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 // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeSecurity(security); d->addCacheNotification(security.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, security); } const MyMoneySecurity& MyMoneyFile::security(const QString& id) const { if (id.isEmpty()) return baseCurrency(); return d->m_cache.security(id); } const QList MyMoneyFile::securityList() const { d->checkStorage(); return d->m_storage->securityList(); } void MyMoneyFile::addCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addCurrency(currency); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadSecurity(currency); d->m_changeSet += MyMoneyNotification(File::Mode::Add, currency); } void MyMoneyFile::modifyCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // force reload of base currency object if (currency.id() == d->m_baseCurrency.id()) d->m_baseCurrency.clearId(); d->m_storage->modifyCurrency(currency); d->addCacheNotification(currency.id()); 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("Cannot delete base currency."); } // FIXME check that security is not referenced by other object // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeCurrency(currency); d->addCacheNotification(currency.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, currency); } const MyMoneySecurity& MyMoneyFile::currency(const QString& id) const { if (id.isEmpty()) return baseCurrency(); const MyMoneySecurity& curr = d->m_cache.security(id); if (curr.id().isEmpty()) { QString msg; msg = QString("Currency '%1' not found.").arg(id); throw MYMONEYEXCEPTION(msg); } return curr; } const QMap MyMoneyFile::ancientCurrencies() const { QMap ancientCurrencies; ancientCurrencies.insert(MyMoneySecurity("ATS", i18n("Austrian Schilling"), "Ö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"))); // http://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: http://www.tf-portfoliosolutions.net/products/turkishlira.aspx ancientCurrencies.insert(MyMoneySecurity("TRL", i18n("Turkish Lira (old)"), "TL"), MyMoneyPrice("TRL", "TRY", QDate(2004, 12, 31), MyMoneyMoney(1, 1000000), QLatin1Literal("KMyMoney"))); // Source: http://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: http://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: http://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; } const 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("AON", i18n("Angolan New Kwanza"))); 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("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("IDR", i18n("Indonesian Rupiah"), "IDR", 1)); 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(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)); currencyList.append(ancientCurrencies().keys()); return currencyList; } const QList MyMoneyFile::currencyList() const { d->checkStorage(); return d->m_storage->currencyList(); } const QString& MyMoneyFile::foreignCurrency(const QString& first, const QString& second) const { if (baseCurrency().id() == second) return first; return second; } const 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()); // clear all changed objects from cache MyMoneyNotifier notifier(d); 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); // 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); // 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); } const MyMoneyPriceList MyMoneyFile::priceList() const { d->checkStorage(); return d->m_storage->priceList(); } bool MyMoneyFile::hasAccount(const QString& id, const QString& name) const { MyMoneyAccount acc = d->m_cache.account(id); QStringList list = acc.accountList(); QStringList::ConstIterator it; bool rc = false; for (it = list.constBegin(); rc == false && it != list.constEnd(); ++it) { MyMoneyAccount a = d->m_cache.account(*it); if (a.name() == name) rc = true; } return rc; } const QList MyMoneyFile::reportList() const { d->checkStorage(); return d->m_storage->reportList(); } void MyMoneyFile::addReport(MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addReport(report); } void MyMoneyFile::modifyReport(const MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifyReport(report); d->addCacheNotification(report.id()); } unsigned MyMoneyFile::countReports() const { d->checkStorage(); return d->m_storage->countReports(); } const 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); MyMoneyNotifier notifier(d); d->m_storage->removeReport(report); d->addCacheNotification(report.id(), false); } const QList MyMoneyFile::budgetList() const { d->checkStorage(); return d->m_storage->budgetList(); } void MyMoneyFile::addBudget(MyMoneyBudget& budget) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addBudget(budget); } const 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifyBudget(budget); d->addCacheNotification(budget.id()); } unsigned MyMoneyFile::countBudgets() const { d->checkStorage(); return d->m_storage->countBudgets(); } const 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); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeBudget(budget); d->addCacheNotification(budget.id(), false); } void MyMoneyFile::addOnlineJob(onlineJob& job) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addOnlineJob(job); d->m_cache.preloadOnlineJob(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); d->addCacheNotification(job.id()); } const onlineJob MyMoneyFile::getOnlineJob(const QString &jobId) const { d->checkStorage(); return d->m_storage->getOnlineJob(jobId); } const 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 MyMoneyNotifier notifier(d); if (job.isLocked()) { return; } d->addCacheNotification(job.id(), false); 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(); } bool MyMoneyFile::addVATSplit(MyMoneyTransaction& transaction, const MyMoneyAccount& account, const MyMoneyAccount& category, const MyMoneyMoney& amount) { bool rc = false; try { MyMoneySplit cat; // category MyMoneySplit tax; // tax if (category.value("VatAccount").isEmpty()) return false; MyMoneyAccount vatAcc = this->account(category.value("VatAccount").toLatin1()); const MyMoneySecurity& asec = security(account.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 = account.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(account.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(account.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()); QList transactions = transactionList(filter); return transactions.count() > 0; } void MyMoneyFile::clearCache() { d->checkStorage(); d->m_cache.clear(); d->m_balanceCache.clear(); } void MyMoneyFile::preloadCache() { d->checkStorage(); d->m_cache.clear(); QList a_list; d->m_storage->accountList(a_list); d->m_cache.preloadAccount(a_list); d->m_cache.preloadPayee(d->m_storage->payeeList()); d->m_cache.preloadTag(d->m_storage->tagList()); d->m_cache.preloadInstitution(d->m_storage->institutionList()); d->m_cache.preloadSecurity(d->m_storage->securityList() + d->m_storage->currencyList()); d->m_cache.preloadSchedule(d->m_storage->scheduleList()); d->m_cache.preloadOnlineJob(d->m_storage->onlineJobList()); } bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const { bool rc = false; if (t.splitCount() == 2) { QList::const_iterator it_s; for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { MyMoneyAccount acc = account((*it_s).accountId()); if (acc.isIncomeExpense()) break; } if (it_s == t.splits().end()) rc = true; } return rc; } bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const { QList::const_iterator it_s; const QList& list = t.splits(); for (it_s = list.begin(); it_s != list.end(); ++it_s) { if (referencesClosedAccount(*it_s)) break; } return it_s != list.end(); } 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; } const QString MyMoneyFile::openingBalancesPrefix() { return i18n("Opening Balances"); } bool MyMoneyFile::hasMatchingOnlineBalance(const MyMoneyAccount& _acc) const { // get current values MyMoneyAccount 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(); } /** * Make sure that the splits value has the precision of the corresponding account */ void MyMoneyFile::fixSplitPrecision(MyMoneyTransaction& t) const { QList::iterator its; MyMoneySecurity transactionSecurity = security(t.commodity()); int transactionFraction = transactionSecurity.smallestAccountFraction(); for(its = t.splits().begin(); its != t.splits().end(); ++its) { MyMoneyAccount acc = account((*its).accountId()); int fraction = acc.fraction(); if(fraction == -1) { MyMoneySecurity sec = security(acc.currencyId()); fraction = acc.fraction(sec); } - (*its).setShares((*its).shares().convertDenominator(fraction).canonicalize()); - (*its).setValue((*its).value().convertDenominator(transactionFraction).canonicalize()); + (*its).setShares(static_cast((*its).shares().convertDenominator(fraction).canonicalize())); + (*its).setValue(static_cast((*its).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() { rollback(); 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/mymoney/mymoneyfile.h b/kmymoney/mymoney/mymoneyfile.h index b1cfedc3c..854c99f35 100644 --- a/kmymoney/mymoney/mymoneyfile.h +++ b/kmymoney/mymoney/mymoneyfile.h @@ -1,1721 +1,1721 @@ /*************************************************************************** mymoneyfile.h ------------------- copyright : (C) 2002, 2007 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef MYMONEYFILE_H #define MYMONEYFILE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" /** * @author Thomas Baumgart, Michael Edwardes, Kevin Tambascio, Christian Dávid */ /** * This class represents the interface to the MyMoney engine. * For historical reasons it is still called MyMoneyFile. * It is implemented using the singleton pattern and thus only * exists once for each running instance of an application. * * The instance of the MyMoneyFile object is accessed as follows: * * @code * MyMoneyFile *file = MyMoneyFile::instance(); * file->anyMemberFunction(); * @endcode * * The first line of the above code creates a unique MyMoneyFile * object if it is called for the first time ever. All subsequent * calls to this functions return a pointer to the object created * during the first call. * * As the MyMoneyFile object represents the business logic, a storage * manager must be attached to it. This mechanism allows to use different * access methods to store the objects. The interface to access such an * storage manager is defined in the class IMyMoneyStorage. The methods * attachStorage() and detachStorage() are used to attach/detach a * storage manager object. The following code can be used to create a * functional MyMoneyFile instance: * * @code * IMyMoneyStorage *storage = .... * MyMoneyFile *file = MyMoneyFile::instance(); * file->attachStorage(storage); * @endcode * * The methods addAccount(), modifyAccount() and removeAccount() implement the * general account maintenance functions. The method reparentAccount() is * available to move an account from one superordinate account to another. * account() and accountList() are used to retrieve a single instance or a * QList of MyMoneyAccount objects. * * The methods addInstitution(), modifyInstitution() and removeInstitution() * implement the general institution maintenance functions. institution() and * institutionList() are used to retrieve a single instance or a * QList of MyMoneyInstitution objects. * * The methods addPayee(), modifyPayee() and removePayee() * implement the general payee maintenance functions. * payee() and payeeList() are used to retrieve a single instance or a * QList of MyMoneyPayee objects. * * The methods addTag(), modifyTag() and removeTag() * implement the general tag maintenance functions. * tag() and tagList() are used to retrieve a single instance or a * QList of MyMoneyTag objects. * * The methods addTransaction(), modifyTransaction() and removeTransaction() * implement the general transaction maintenance functions. * transaction() and transactionList() are used to retrieve * a single instance or a QList of MyMoneyTransaction objects. * * The methods addSecurity(), modifySecurity() and removeSecurity() * implement the general access to equities held in the engine. * * The methods addCurrency(), modifyCurrency() and removeCurrency() * implement the general access to multiple currencies held in the engine. * The methods baseCurrency() and setBaseCurrency() allow to retrieve/set * the currency selected by the user as base currency. If a currency * reference is emtpy, it will usually be interpreted as baseCurrency(). * * The methods liability(), asset(), expense(), income() and equity() are * used to retrieve the five standard accounts. isStandardAccount() * checks if a given accountId references one of the or not. * setAccountName() is used to specify a name for the standard accounts * from the GUI. * * The MyMoneyFile object emits the dataChanged() signal when data * has been changed. * * For abritrary values that have to be stored with the storage object * but are of importance to the application only, the object is derived * for MyMoneyKeyValueContainer which provides a container to store * these values indexed by an alphanumeric key. * * @exception MyMoneyException is thrown whenever an error occurs * while the engine code is running. The MyMoneyException:: object * describes the problem. */ template class QMap; class QString; class QStringList; class QBitArray; class IMyMoneyStorage; class MyMoneyCostCenter; class MyMoneyAccount; class MyMoneyInstitution; class MyMoneySecurity; class MyMoneyPrice; typedef QPair MyMoneySecurityPair; typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; class MyMoneySchedule; class MyMoneyTag; class MyMoneyPayee; class MyMoneyBudget; class MyMoneyReport; class MyMoneyMoney; class MyMoneySplit; class MyMoneyObject; class MyMoneyTransaction; class MyMoneyTransactionFilter; class onlineJob; namespace eMyMoney { namespace Account { enum class Type; } namespace File { enum class Object; } namespace Schedule { enum class Type; enum class Occurrence; enum class PaymentType; } namespace TransactionFilter { enum class State; } } class KMM_MYMONEY_EXPORT MyMoneyFile : public QObject { Q_OBJECT KMM_MYMONEY_UNIT_TESTABLE public: friend class MyMoneyNotifier; /** * This is the function to access the MyMoneyFile object. * It returns a pointer to the single instance of the object. */ static inline MyMoneyFile* instance() { return &file; } /** * This is the destructor for any MyMoneyFile object */ ~MyMoneyFile(); /** * @deprecated This is a convenience constructor. Do not use it anymore. * It will be deprecated in a future version of the engine. * * @param storage pointer to object that implements the IMyMoneyStorage * interface. */ - MyMoneyFile(IMyMoneyStorage *storage); + explicit MyMoneyFile(IMyMoneyStorage *storage); // general get functions const MyMoneyPayee& user() const; // general set functions void setUser(const MyMoneyPayee& user); /** * This method is used to attach a storage object to the MyMoneyFile object * Without an attached storage object, the MyMoneyFile object is * of no use. * * After successful completion, the dataChanged() signal is emitted. * * In case of an error condition, an exception is thrown. * The following error conditions are checked: * * - @a storage is not equal to 0 * - there is no other @a storage object attached (use detachStorage() * to revert the attachStorage() operation. * * @param storage pointer to object that implements the IMyMoneyStorage * interface. * * @sa detachStorage() */ void attachStorage(IMyMoneyStorage* const storage); /** * This method is used to detach a previously attached storage * object from the MyMoneyFile object. If no storage object * is attached to the engine, this is a NOP. * * @param storage pointer to object that implements the IMyMoneyStorage * interface. * * @sa attachStorage() */ void detachStorage(IMyMoneyStorage* const storage = 0); /** * This method returns whether a storage is currently attached to * the engine or not. * * @return true if storage object is attached, false otherwise */ bool storageAttached() const; /** * This method returns a pointer to the storage object * * @return const pointer to the current attached storage object. * If no object is attached, returns 0. */ IMyMoneyStorage* storage() const; /** * This method must be called before any single change or a series of changes * in the underlying storage area is performed. * Once all changes are complete (i.e. the transaction is completed), * commitTransaction() must be called to finalize all changes. If an error occurs * during the processing of the changes call rollbackTransaction() to undo the * changes done so far. */ void startTransaction(); /** * This method returns whether a transaction has been started (@a true) * or not (@a false). */ bool hasTransaction() const; /** * @sa startTransaction() */ void commitTransaction(); /** * @sa startTransaction() */ void rollbackTransaction(); /** * This method is used to return the standard liability account * @return MyMoneyAccount liability account(group) */ const MyMoneyAccount& liability() const; /** * This method is used to return the standard asset account * @return MyMoneyAccount asset account(group) */ const MyMoneyAccount& asset() const; /** * This method is used to return the standard expense account * @return MyMoneyAccount expense account(group) */ const MyMoneyAccount& expense() const; /** * This method is used to return the standard income account * @return MyMoneyAccount income account(group) */ const MyMoneyAccount& income() const; /** * This method is used to return the standard equity account * @return MyMoneyAccount equity account(group) */ const MyMoneyAccount& equity() const; /** * This method returns the account information for the opening * balances account for the given @p security. If the respective * account does not exist, it will be created. The name is constructed * using MyMoneyFile::openingBalancesPrefix() and appending " (xxx)" in * case the @p security is not the baseCurrency(). The account created * will be a sub-account of the standard equity account provided by equity(). * * @param security Security for which the account is searched * * @return The opening balance account * * @note No notifications will be sent! */ const MyMoneyAccount openingBalanceAccount(const MyMoneySecurity& security); /** * This method is essentially the same as the above, except it works on * const objects. If there is no opening balance account, this method * WILL NOT create one. Instead it will thrown an exception. * * @param security Security for which the account is searched * * @return The opening balance account * * @note No notifications will be sent! */ const MyMoneyAccount openingBalanceAccount(const MyMoneySecurity& security) const; /** * Create an opening balance transaction for the account @p acc * with a value of @p balance. If the corresponding opening balance account * for the account's currency does not exist it will be created. If it exists * and it's opening date is later than the opening date of @p acc, * the opening date of the opening balances account will be adjusted to the * one of @p acc. * * @param acc reference to account for which the opening balance transaction * should be created * @param balance reference to the value of the opening balance transaction * * @returns The created MyMoneyTransaction object. In case no transaction has been * created, the id of the object is empty. */ MyMoneyTransaction createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance); /** * Retrieve the opening balance transaction for the account @p acc. * If there is no opening balance transaction, QString() will be returned. * * @param acc reference to account for which the opening balance transaction * should be retrieved * @return QString id for the transaction, or QString() if no transaction exists */ QString openingBalanceTransaction(const MyMoneyAccount& acc) const; /** * This method returns an indicator if the MyMoneyFile object has been * changed after it has last been saved to permanent storage. * * @return true if changed, false if not */ bool dirty() const; /** * This method is used to force the attached storage object to * be dirty. This is used by the application to re-set the dirty * flag after a failed upload to a server when the save operation * to a local temp file was OK. */ void setDirty() const; /** * Adds an institution to the file-global institution pool. A * respective institution-ID will be generated for this object. * The ID is stored as QString in the object passed as argument. * * An exception will be thrown upon error conditions. * @param institution The complete institution information in a * MyMoneyInstitution object */ void addInstitution(MyMoneyInstitution& institution); /** * Modifies an already existing institution in the file global * institution pool. * * An exception will be thrown upon error conditions. * * @param institution The complete new institution information */ void modifyInstitution(const MyMoneyInstitution& institution); /** * Deletes an existing institution from the file global institution pool * Also modifies the accounts that reference this institution as * their institution. * * An exception will be thrown upon error conditions. * * @param institution institution to be deleted. */ void removeInstitution(const MyMoneyInstitution& institution); void createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal); /** * Adds an account to the file-global account pool. A respective * account-ID will be generated within this record. The modified * members of @a account will be updated. * * A few parameters of the account to be added are checked against * the following conditions. If they do not match, an exception is * thrown. * * An account must match the following conditions: * * a) the account must have a name with length > 0 * b) the account must not have an id assigned * c) the transaction list must be empty * d) the account must not have any sub-ordinate accounts * e) the account must have no parent account * f) the account must not have any reference to a MyMoneyFile object * * An exception will be thrown upon error conditions. * * @param account The complete account information in a MyMoneyAccount object * @param parent The complete account information of the parent account */ void addAccount(MyMoneyAccount& account, MyMoneyAccount& parent); /** * Modifies an already existing account in the file global account pool. * * An exception will be thrown upon error conditions. * * @param account reference to the new account information */ void modifyAccount(const MyMoneyAccount& account); /** * This method re-parents an existing account * * An exception will be thrown upon error conditions. * * @param account MyMoneyAccount reference to account to be re-parented * @param parent MyMoneyAccount reference to new parent account */ void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent); /** * moves splits from one account to another * * @param oldAccount id of the current account * @param newAccount if of the new account * * @return the number of modified splits */ unsigned int moveSplits(const QString& oldAccount, const QString& newAccount); /** * This method is used to determince, if the account with the * given ID is referenced by any split in m_transactionList. * * @param id id of the account to be checked for * @return true if account is referenced, false otherwise */ bool hasActiveSplits(const QString& id) const; /** * This method is used to check whether a given * account id references one of the standard accounts or not. * * An exception will be thrown upon error conditions. * * @param id account id * @return true if account-id is one of the standards, false otherwise */ bool isStandardAccount(const QString& id) const; /** * Returns @a true, if transaction @p t is a transfer transaction. * A transfer transaction has two splits, both referencing either * an asset, a liability or an equity account. */ bool isTransfer(const MyMoneyTransaction& t) const; /** * This method is used to set the name for the specified standard account * within the storage area. An exception will be thrown, if an error * occurs * * @param id QString reference to one of the standard accounts. * @param name QString reference to the name to be set * */ void setAccountName(const QString& id, const QString& name) const; /** * Deletes an existing account from the file global account pool * This method only allows to remove accounts that are not * referenced by any split. Use moveSplits() to move splits * to another account. An exception is thrown in case of a * problem. * * @param account reference to the account to be deleted. */ void removeAccount(const MyMoneyAccount& account); /** * Deletes existing accounts and their subaccounts recursivly * from the global account pool. * This method expects that all accounts and their subaccounts * are no longer assigned to any transactions or splits. * An exception is thrown in case of a problem deleting an account. * * The optional parameter level is used to keep track of the recursion level. * If the recursion level exceeds 100 (some arbitrary number which seems a good * maximum), an exception is thrown. * * @param account_list Reference to a list of account IDs to be deleted. * @param level Parameter to keep track of recursion level (do not pass a value here). */ void removeAccountList(const QStringList& account_list, unsigned int level = 0); /** * This member function checks all accounts identified by account_list * and their subaccounts whether they are assigned to transactions/splits or not. * The function calls itself recursively with the list of sub-accounts of * the currently processed account. * * The optional parameter level is used to keep track of the recursion level. * If the recursion level exceeds 100 (some arbitrary number which seems a good * maximum), an exception is thrown. * * @param account_list A QStringList with account IDs that need to be checked. * @param level (optional) Optional parameter to indicate recursion level. * @return Returns 'false' if at least one account has been found that * is still referenced by a transaction. */ bool hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level = 0); /** * Adds a transaction to the file-global transaction pool. A respective * transaction-ID will be generated for this object. The ID is stored * as QString in the object passed as argument. * Splits must reference valid accounts and valid payees. The payee * id can be empty. * * An exception will be thrown upon error conditions. * * @param transaction reference to the transaction */ void addTransaction(MyMoneyTransaction& transaction); /** * This method is used to update a specific transaction in the * transaction pool of the MyMoneyFile object. * Splits must reference valid accounts and valid payees. The payee * id can be empty. * * An exception will be thrown upon error conditions. * * @param transaction reference to transaction to be changed */ void modifyTransaction(const MyMoneyTransaction& transaction); /** * This method is used to extract a transaction from the file global * transaction pool through an id. In case of an invalid id, an * exception will be thrown. * * @param id id of transaction as QString. * @return reference to the requested transaction */ const MyMoneyTransaction transaction(const QString& id) const; /** * This method is used to extract a transaction from the file global * transaction pool through an index into an account. * * @param account id of the account as QString * @param idx number of transaction in this account * @return reference to MyMoneyTransaction object */ const MyMoneyTransaction transaction(const QString& account, const int idx) const; /** * This method is used to pull a list of transactions from the file * global transaction pool. It returns all those transactions * that match the filter passed as argument. If the filter is empty, * the whole journal will be returned. * The list returned is sorted according to the transactions posting date. * If more than one transaction exists for the same date, the order among * them is undefined. * * @param filter MyMoneyTransactionFilter object with the match criteria * * @return set of transactions in form of a QList */ const QList transactionList(MyMoneyTransactionFilter& filter) const; void transactionList(QList& list, MyMoneyTransactionFilter& filter) const; void transactionList(QList >& list, MyMoneyTransactionFilter& filter) const; /** * This method is used to remove a transaction from the transaction * pool (journal). * * @param transaction const reference to transaction to be deleted */ void removeTransaction(const MyMoneyTransaction& transaction); /** * This method is used to return the actual balance of an account * without it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date (default = QDate()) * @return balance of the account as MyMoneyMoney object */ const MyMoneyMoney balance(const QString& id, const QDate& date) const; const MyMoneyMoney balance(const QString& id) const; /** * This method is used to return the cleared balance of an account * without it's sub-ordinate accounts for a specific date. All * recorded transactions are included in the balance. * This method is used by the reconcialition functionality * * @param id id of the account in question * @param date return cleared balance for specific date * @return balance of the account as MyMoneyMoney object */ const MyMoneyMoney clearedBalance(const QString& id, const QDate& date) const; /** * This method is used to return the actual balance of an account * including it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date (default = QDate()) * @return balance of the account as MyMoneyMoney object */ const MyMoneyMoney totalBalance(const QString& id, const QDate& date) const; const MyMoneyMoney totalBalance(const QString& id) const; /** * This method returns the number of transactions currently known to file * in the range 0..MAXUINT * * @param account QString reference to account id. If account is empty + all transactions (the journal) will be counted. If account * is not empty it returns the number of transactions * that have splits in this account. * * @return number of transactions in journal/account */ unsigned int transactionCount(const QString& account) const; unsigned int transactionCount() const; /** * This method returns a QMap filled with the number of transactions * per account. The account id serves as index into the map. If one * needs to have all transactionCounts() for many accounts, this method * is faster than calling transactionCount(const QString& account) many * times. * * @return QMap with numbers of transactions per account */ const QMap transactionCountMap() const; /** * This method returns the number of institutions currently known to file * in the range 0..MAXUINT * * @return number of institutions known to file */ unsigned int institutionCount() const; /** * This method returns the number of accounts currently known to file * in the range 0..MAXUINT * * @return number of accounts currently known inside a MyMoneyFile object */ unsigned int accountCount() const; /** * Returns the institution of a given ID * * @param id id of the institution to locate * @return MyMoneyInstitution object filled with data. If the institution * could not be found, an exception will be thrown */ const MyMoneyInstitution& institution(const QString& id) const; /** * This method returns a list of the institutions * inside a MyMoneyFile object * * @param list reference to the list. It will be cleared by this method first */ void institutionList(QList& list) const; /** * This method returns a list of the institutions * inside a MyMoneyFile object. This is a convenience method * to the one above * * @return QList containing the institution objects */ const QList institutionList() const; /** * Returns the account addressed by its id. * * @param id id of the account to locate. * @return MyMoneyAccount object carrying the @p id. An exception is thrown * if the id is unknown */ const MyMoneyAccount& account(const QString& id) const; /** * Returns the account addressed by its name. * * @param name name of the account to locate. * @return First MyMoneyAccount object found carrying the @p name. * An empty MyMoneyAccount object will be returned if the name is not found. */ const MyMoneyAccount& accountByName(const QString& name) const; /** * Returns the sub-account addressed by its name. * * @param acc account to search in * @param name name of the account to locate. * @return First MyMoneyAccount object found carrying the @p name. * An empty MyMoneyAccount object will be returned if the name is not found. */ const MyMoneyAccount& subAccountByName(const MyMoneyAccount& acc, const QString& name) const; /** * This method returns a list of accounts inside a MyMoneyFile object. * An optional parameter is a list of id's. If this list is emtpy (the default) * the returned list contains all accounts, otherwise only those referenced * in the id-list. * * @param list reference to QList receiving the account objects * @param idlist QStringList of account ids of those accounts that * should be returned. If this list is empty, all accounts * currently known will be returned. * * @param recursive if @p true, then recurse in all found accounts. The default is @p false */ void accountList(QList& list, const QStringList& idlist = QStringList(), const bool recursive = false) const; /** * This method is used to convert an account id to a string representation * of the names which can be used as a category description. If the account * is part of a hierarchy, the category name will be the concatenation of * the single account names separated by MyMoneyAccount::AccountSeperator. * * @param accountId QString reference of the account's id * @param includeStandardAccounts if true, the standard top account will be part * of the name, otherwise it will not be included (default is @c false) * * @return QString of the constructed name. */ QString accountToCategory(const QString& accountId, bool includeStandardAccounts = false) const; /** * This method is used to convert a string representing a category to * an account id. A category can be the concatenation of multiple accounts * representing a hierarchy of accounts. They have to be separated by * MyMoneyAccount::AccountSeperator. * * @param category const reference to QString containing the category * @param type account type if a specific type is required (defaults to Unknown) * * @return QString of the corresponding account. If account was not found * the return value will be an empty string. */ QString categoryToAccount(const QString& category, eMyMoney::Account::Type type) const; QString categoryToAccount(const QString& category) const; /** * This method is used to convert a string representing an asset or * liability account to an account id. An account name can be the * concatenation of multiple accounts representing a hierarchy of * accounts. They have to be separated by MyMoneyAccount::AccountSeperator. * * @param name const reference to QString containing the account name * * @return QString of the corresponding account. If account was not found * the return value will be an empty string. */ QString nameToAccount(const QString& name) const; /** * This method is used to extract the parent part of an account hierarchy * name who's parts are separated by MyMoneyAccount::AccountSeperator. * * @param name full account name * @return parent name (full account name excluding the last part) */ QString parentName(const QString& name) const; /** * This method is used to create a new payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void addPayee(MyMoneyPayee& payee); /** * This method is used to retrieve information about a payee * An exception will be thrown upon error conditions. * * @param id QString reference to id of payee * * @return MyMoneyPayee object of payee */ const MyMoneyPayee& payee(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a payee/receiver. * An exception will be thrown upon error conditions. * * @param payee QString reference to name of payee * * @return MyMoneyPayee object of payee */ const MyMoneyPayee& payeeByName(const QString& payee) const; /** * This method is used to modify an existing payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void modifyPayee(const MyMoneyPayee& payee); /** * This method is used to remove an existing payee. * An error condition occurs, if the payee is still referenced * by a split. * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void removePayee(const MyMoneyPayee& payee); /** * This method returns a list of the payees * inside a MyMoneyStorage object * * @return QList containing the payee information */ const QList payeeList() const; /** * This method is used to create a new tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void addTag(MyMoneyTag& tag); /** * This method is used to retrieve information about a tag * An exception will be thrown upon error conditions. * * @param id QString reference to id of tag * * @return MyMoneyTag object of tag */ const MyMoneyTag& tag(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a tag. * An exception will be thrown upon error conditions. * * @param tag QString reference to name of tag * * @return MyMoneyTag object of tag */ const MyMoneyTag& tagByName(const QString& tag) const; /** * This method is used to modify an existing tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void modifyTag(const MyMoneyTag& tag); /** * This method is used to remove an existing tag. * An error condition occurs, if the tag is still referenced * by a split. * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void removeTag(const MyMoneyTag& tag); /** * This method returns a list of the tags * inside a MyMoneyStorage object * * @return QList containing the tag information */ const QList tagList() const; /** * This method returns a list of the cost centers * inside a MyMoneyStorage object * * @return QList containing the cost center information */ void costCenterList(QList< MyMoneyCostCenter >& list) const; /** * This method is used to extract a value from the storage's * KeyValueContainer. For details see MyMoneyKeyValueContainer::value(). * @note Do not use this method to return the value of the key @p kmm-id. Use * storageId() instead. * * @param key const reference to QString containing the key * @return QString containing the value */ QString value(const QString& key) const; /** * This method is used to set a value in the storage's * KeyValueContainer. For details see MyMoneyKeyValueContainer::setValue(). * * @param key const reference to QString containing the key * @param val const reference to QString containing the value * * @note Keys starting with the leadin @p kmm- are reserved for internal use * by the MyMoneyFile object. */ void setValue(const QString& key, const QString& val); /** * This method returns the unique id of the attached storage object. * In case the storage object does not have an id yet, a new one will be * assigned. * * @return QString containing the value * * An exception is thrown if no storage object is attached. */ QString storageId(); /** * This method is used to delete a key-value-pair from the * storage's KeyValueContainer identified by the parameter * @p key. For details see MyMoneyKeyValueContainer::deletePair(). * * @param key const reference to QString containing the key */ void deletePair(const QString& key); /** * This method is used to add a scheduled transaction to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param sched reference to the MyMoneySchedule object */ void addSchedule(MyMoneySchedule& sched); /** * This method is used to modify an existing MyMoneySchedule * object. Therefor, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ void modifySchedule(const MyMoneySchedule& sched); /** * This method is used to remove an existing MyMoneySchedule object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ void removeSchedule(const MyMoneySchedule& sched); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ const MyMoneySchedule schedule(const QString& id) const; /** * This method is used to extract a list of scheduled transactions * according to the filter criteria passed as arguments. * * @param accountId only search for scheduled transactions that reference * account @p accountId. If accountId is the empty string, * this filter is off. Default is @p QString(). * @param type only schedules of type @p type are searched for. * See eMyMoney::Schedule::Type for details. * Default is eMyMoney::Schedule::Type::Any * @param occurrence only schedules of occurrence type @p occurrence are searched for. * See eMyMoney::Schedule::Occurence for details. * Default is eMyMoney::Schedule::Occurrence::Any * @param paymentType only schedules of payment method @p paymentType * are searched for. * See eMyMoney::Schedule::PaymentType for details. * Default is eMyMoney::Schedule::PaymentType::Any * @param startDate only schedules with payment dates after @p startDate * are searched for. Default is all dates (QDate()). * @param endDate only schedules with payment dates ending prior to @p endDate * are searched for. Default is all dates (QDate()). * @param overdue if true, only those schedules that are overdue are * searched for. Default is false (all schedules will be returned). * * @return const QList list of schedule objects. */ const QList scheduleList(const QString& accountId, const eMyMoney::Schedule::Type type, const eMyMoney::Schedule::Occurrence occurrence, const eMyMoney::Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, const bool overdue) const; const QList scheduleList(const QString& accountId) const; const QList scheduleList() const; const QStringList consistencyCheck(); /** * MyMoneyFile::openingBalancesPrefix() is a special string used * to generate the name for opening balances accounts. See openingBalanceAccount() * for details. */ static const QString openingBalancesPrefix(); /** * MyMoneyFile::AccountSeperator is used as the separator * between account names to form a hierarchy. */ static const QString AccountSeperator; /** * createCategory creates a category from a text name. * * The whole account hierarchy is created if it does not * already exist. e.g if name = Bills:Credit Card and * base = expense(), Bills will first be checked to see if * it exists and created if not. Credit Card will then * be created with Bills as it's parent. The Credit Card account * will have it's id returned. * * @param base The base account (expense or income) * @param name The category to create * * @return The category account id or empty on error. * * @exception An exception will be thrown, if @p base is not equal * expense() or income(). **/ QString createCategory(const MyMoneyAccount& base, const QString& name); /** * This method is used to get the account id of the split for * a transaction from the text found in the QIF $ or L record. * If an account with the name is not found, the user is asked * if it should be created. * * @param name name of account as found in the QIF file * @param value value found in the T record * @param value2 value found in the $ record for split transactions * * @return id of the account for the split. If no name is specified * or the account was not found and not created the * return value will be "". */ QString checkCategory(const QString& name, const MyMoneyMoney& value, const MyMoneyMoney& value2); const QList scheduleListEx(int scheduleTypes, int scheduleOcurrences, int schedulePaymentTypes, QDate startDate, const QStringList& accounts = QStringList()) const; /** * This method is used to add a new security object to the engine. * The ID of the object is the trading symbol, so there is no need for an additional * ID since the symbol is guaranteed to be unique. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object */ void addSecurity(MyMoneySecurity& security); /** * This method is used to modify an existing MyMoneySchedule * object. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be updated */ void modifySecurity(const MyMoneySecurity& security); /** * This method is used to remove an existing MyMoneySecurity object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be removed */ void removeSecurity(const MyMoneySecurity& security); /** * This method is used to retrieve a single MyMoneySecurity object. * The id of the object must be supplied in the parameter @p id. * If no security with the given id is found, then a corresponding * currency is searched. If @p id is empty, the baseCurrency() is returned. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySecurity object * @return MyMoneySecurity object */ const MyMoneySecurity& security(const QString& id) const; /** * This method is used to retrieve a list of all MyMoneySecurity objects. */ const QList securityList() const; /** * This method is used to add a new currency object to the engine. * The ID of the object is the trading symbol, so there is no need for an additional * ID since the symbol is guaranteed to be unique. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneySecurity object */ void addCurrency(const MyMoneySecurity& currency); /** * This method is used to modify an existing MyMoneySecurity * object. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneySecurity object */ void modifyCurrency(const MyMoneySecurity& currency); /** * This method is used to remove an existing MyMoneySecurity object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneySecurity object */ void removeCurrency(const MyMoneySecurity& currency); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * If @p id is empty, this method returns baseCurrency(). * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ const MyMoneySecurity& currency(const QString& id) const; /** * This method is used to retrieve the map of ancient currencies (together with their last prices) * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QMap of all MyMoneySecurity and MyMoneyPrice objects. */ const QMap ancientCurrencies() const; /** * This method is used to retrieve the list of available currencies * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneySecurity objects. */ const QList availableCurrencyList() const; /** * This method is used to retrieve the list of stored currencies * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneySecurity objects. */ const QList currencyList() const; /** * This method retrieves a MyMoneySecurity object representing * the selected base currency. If the base currency is not * selected (e.g. due to a previous call to setBaseCurrency()) * a standard MyMoneySecurity object will be returned. See * MyMoneySecurity() for details. * * An exception will be thrown upon erroneous situations. * * @return MyMoneySecurity describing base currency */ const MyMoneySecurity& baseCurrency() const; /** * This method returns the foreign currency of the given two * currency ids. If second is the base currency id then @a first * is returned otherwise @a second is returned. */ const QString& foreignCurrency(const QString& first, const QString& second) const; /** * This method allows to select the base currency. It does * not perform any changes to the data in the engine. It merely * stores a reference to the base currency. The currency * passed as argument must exist in the engine. * * An exception will be thrown upon erroneous situations. * * @param currency */ void setBaseCurrency(const MyMoneySecurity& currency); /** * This method adds/replaces a price to/from the price list */ void addPrice(const MyMoneyPrice& price); /** * This method removes a price from the price list */ void removePrice(const MyMoneyPrice& price); /** * This method is used to retrieve a price for a specific security * on a specific date. If there is no price for this date, the last * known price for this currency is used. If no price information * is available, 1.0 will be returned as price. * * @param fromId the id of the currency in question * @param toId the id of the currency to convert to (if emtpy, baseCurrency) * @param date the date for which the price should be returned (default = today) * @param exactDate if true, entry for date must exist, if false any price information * with a date less or equal to @p date will be returned * * @return price found as MyMoneyPrice object * @note This throws an exception when the base currency is not set and toId is empty */ MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate = false) const; MyMoneyPrice price(const QString& fromId, const QString& toId) const; MyMoneyPrice price(const QString& fromId) const; /** * This method returns a list of all prices. * * @return MyMoneyPriceList of all MyMoneyPrice objects. */ const MyMoneyPriceList priceList() const; /** * This method allows to interrogate the engine, if a known account * with id @p id has a subaccount with the name @p name. * * @param id id of the account to look at * @param name account name that needs to be searched force * @retval true account with name @p name found as subaccounts * @retval false no subaccount present with that name */ bool hasAccount(const QString& id, const QString& name) const; /** * This method is used to retrieve the list of all reports * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyReport objects. */ const QList reportList() const; /** * Adds a report to the file-global institution pool. A * respective report-ID will be generated for this object. * The ID is stored as QString in the object passed as argument. * * An exception will be thrown upon error conditions. * * @param report The complete report information in a * MyMoneyReport object */ void addReport(MyMoneyReport& report); /** * Modifies an already existing report in the file global * report pool. * * An exception will be thrown upon error conditions. * * @param report The complete new report information */ void modifyReport(const MyMoneyReport& report); /** * This method returns the number of reports currently known to file * in the range 0..MAXUINT * * @return number of reports known to file */ unsigned countReports() const; /** * This method is used to retrieve a single MyMoneyReport object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyReport object * @return MyMoneyReport object */ const MyMoneyReport report(const QString& id) const; /** * This method is used to remove an existing MyMoneyReport object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param report const reference to the MyMoneyReport object to be updated */ void removeReport(const MyMoneyReport& report); /** * This method is used to retrieve the list of all budgets * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyBudget objects. */ const QList budgetList() const; /** * Adds a budget to the file-global institution pool. A * respective budget-ID will be generated for this object. * The ID is stored as QString in the object passed as argument. * * An exception will be thrown upon error conditions. * * @param budget The complete budget information in a * MyMoneyBudget object */ void addBudget(MyMoneyBudget& budget); /** * This method is used to retrieve the id to a corresponding * name of a budget. * An exception will be thrown upon error conditions. * * @param budget QString reference to name of budget * * @return MyMoneyBudget refernce to object of budget */ const MyMoneyBudget budgetByName(const QString& budget) const; /** * Modifies an already existing budget in the file global * budget pool. * * An exception will be thrown upon error conditions. * * @param budget The complete new budget information */ void modifyBudget(const MyMoneyBudget& budget); /** * This method returns the number of budgets currently known to file * in the range 0..MAXUINT * * @return number of budgets known to file */ unsigned countBudgets() const; /** * This method is used to retrieve a single MyMoneyBudget object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyBudget object * @return MyMoneyBudget object */ const MyMoneyBudget budget(const QString& id) const; /** * This method is used to remove an existing MyMoneyBudget object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param budget const reference to the MyMoneyBudget object to be updated */ void removeBudget(const MyMoneyBudget& budget); /** * This method is used to add a VAT split to a transaction. * * @param transaction reference to the transaction * @param account reference to the account * @param category reference to the category * @param amount reference to the amount of the VAT split * * @return true if a VAT split has been added */ bool addVATSplit(MyMoneyTransaction& transaction, const MyMoneyAccount& account, const MyMoneyAccount& category, const MyMoneyMoney& amount); /** * This method checks, if the given @p object is referenced * by another engine object. * * @param obj const reference to object to be checked * @param skipCheck QBitArray with eStorage::Reference bits set for which * the check should be skipped * * @retval false @p object is not referenced * @retval true @p institution is referenced */ bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const; bool isReferenced(const MyMoneyObject& obj) const; /** * Returns true if any of the accounts referenced by the splits * of transaction @a t is closed. */ bool referencesClosedAccount(const MyMoneyTransaction& t) const; /** * Returns true if the accounts referenced by the split @a s is closed. */ bool referencesClosedAccount(const MyMoneySplit& s) const; /** * This method checks if the given check no &p no is used in * a transaction referencing account &p accId. If @p accId is empty, * @p false is returned. * * @param accId id of account to checked * @param no check number to be verified if used or not * @retval false @p no is not in use * @retval true @p no is already assigned */ bool checkNoUsed(const QString& accId, const QString& no) const; /** * This method returns the highest assigned check no for * account @p accId. * * @param accId id of account to be scanned * @return highest check no. used */ QString highestCheckNo(const QString& accId) const; /** * This method checks if there is a transaction * after the date @p date for account @p accId. * * @param accId id of account to be scanned * @param date date to compare with * @retval false if there is no transaction after @p date * @retval true if there is a transaction after @p date */ bool hasNewerTransaction(const QString& accId, const QDate& date) const; /** * Clear all internal caches (used internally for performance measurements) */ void clearCache(); void forceDataChanged() { emit dataChanged(); } void preloadCache(); /** * This returns @p true if file and online balance of a specific * @p account are matching. Returns false if there is no online balance. * * @param account @p account to be checked * @retval false if @p account has balance mismatch or if there is no online balance. * @retval true if @p account has matching balances */ bool hasMatchingOnlineBalance(const MyMoneyAccount& account) const; /** * This returns the number of transactions of a specific reconciliation state @p state of account with id @p accId. * * @param accId @p accId is the account id of account to be checked * @param state @p state reconciliation state * @return number of transactions with state @p state */ int countTransactionsWithSpecificReconciliationState(const QString& accId, eMyMoney::TransactionFilter::State state) const; /** * @brief Saves a new onlineJob * @param job you stay owner of the object (a copy will be created) */ void addOnlineJob(onlineJob& job); /** * @brief Saves a onlineJob * @param job you stay owner of the object (a copy will be created) */ void modifyOnlineJob(const onlineJob job); /** * @brief Returns onlineJob identified by jobId * @param jobId * @return */ const onlineJob getOnlineJob(const QString &jobId) const; /** * @brief Returns all onlineJobs * @return all online jobs, caller gains ownership */ const QList onlineJobList() const; /** * @brief Returns the number of onlineJobs */ int countOnlineJobs() const; /** * @brief Remove onlineJob * * @note Removing an onlineJob fails if it is locked */ void removeOnlineJob(const onlineJob& job); /** * @brief Removes multiple onlineJobs by id * * @note Removing an onlineJob fails if it is locked */ void removeOnlineJob(const QStringList onlineJobIds); protected: /** * This is the constructor for a new empty file description */ MyMoneyFile(); Q_SIGNALS: /** * This signal is emitted when a transaction has been committed and * the notifications are about to be sent out. */ void beginChangeNotification(); /** * This signal is emitted when a transaction has been committed and * all notifications have been sent out. */ void endChangeNotification(); /** * This signal is emitted whenever any data has been changed in the engine * via any of the methods of this object */ void dataChanged(); /** * This signal is emitted by the engine whenever a new object * had been added. The data for the new object is contained in * @a obj. */ void objectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj); /** * This signal is emitted by the engine whenever an object * had been removed. * * @note: The data contained in @a obj is only for reference * purposes and should not be used to call any MyMoneyFile * method anymore as the object is already deleted in the storage * when the signal is emitted. */ void objectRemoved(eMyMoney::File::Object objType, const QString& id); /** * This signal is emitted by the engine whenever an object * had been changed. The new state of the object is contained * in @a obj. */ void objectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj); /** * This signal is emitted by the engine whenever the balance * of an account had been changed by adding, modifying or * removing transactions from the MyMoneyFile object. */ void balanceChanged(const MyMoneyAccount& acc); /** * This signal is emitted by the engine whenever the value * of an account had been changed by adding or removing * prices from the MyMoneyFile object. */ void valueChanged(const MyMoneyAccount& acc); private: static MyMoneyFile file; MyMoneyFile& operator=(MyMoneyFile&); // not allowed for singleton MyMoneyFile(const MyMoneyFile&); // not allowed for singleton QString locateSubAccount(const MyMoneyAccount& base, const QString& category) const; void ensureDefaultCurrency(MyMoneyAccount& acc) const; void warningMissingRate(const QString& fromId, const QString& toId) const; /** * This method creates an opening balances account. The name is constructed * using MyMoneyFile::openingBalancesPrefix() and appending " (xxx)" in * case the @p security is not the baseCurrency(). The account created * will be a sub-account of the standard equity account provided by equity(). * * @param security Security for which the account is searched */ const MyMoneyAccount createOpeningBalanceAccount(const MyMoneySecurity& security); const MyMoneyAccount openingBalanceAccount_internal(const MyMoneySecurity& security) const; /** * Make sure that the splits value has the precision of the corresponding account */ void fixSplitPrecision(MyMoneyTransaction& t) const; private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; }; class MyMoneyFileTransactionPrivate; class KMM_MYMONEY_EXPORT MyMoneyFileTransaction { Q_DISABLE_COPY(MyMoneyFileTransaction) public: MyMoneyFileTransaction(); ~MyMoneyFileTransaction(); /** * Commit the current transaction. * * @warning Make sure not to use any variable that might have been altered by * the transaction. Please keep in mind, that changing transactions * can also affect account objects. If you still need those variables * just reload them from the engine. */ void commit(); void rollback(); void restart(); private: MyMoneyFileTransactionPrivate * const d_ptr; Q_DECLARE_PRIVATE(MyMoneyFileTransaction) }; #endif diff --git a/kmymoney/mymoney/mymoneymoney.cpp b/kmymoney/mymoney/mymoneymoney.cpp index 23e5b4929..0f782c9ac 100644 --- a/kmymoney/mymoney/mymoneymoney.cpp +++ b/kmymoney/mymoney/mymoneymoney.cpp @@ -1,329 +1,329 @@ /*************************************************************************** mymoneymymoney.cpp - description ------------------- begin : Thu Feb 21 2002 copyright : (C) 2000-2002 by Michael Edwardes (C) 2011 by Carlos Eduardo da Silva (C) 2001-2017 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ // make sure, that this is defined before we even include any other header file #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS // force definition of min and max values #endif #include "mymoneymoney.h" #include // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes const MyMoneyMoney MyMoneyMoney::ONE = MyMoneyMoney(1, 1); const MyMoneyMoney MyMoneyMoney::MINUS_ONE = MyMoneyMoney(-1, 1); QChar MyMoneyMoney::_thousandSeparator = ','; QChar MyMoneyMoney::_decimalSeparator = '.'; MyMoneyMoney::signPosition MyMoneyMoney::_negativeMonetarySignPosition = BeforeQuantityMoney; MyMoneyMoney::signPosition MyMoneyMoney::_positiveMonetarySignPosition = BeforeQuantityMoney; bool MyMoneyMoney::_negativePrefixCurrencySymbol = false; bool MyMoneyMoney::_positivePrefixCurrencySymbol = false; MyMoneyMoney::fileVersionE MyMoneyMoney::_fileVersion = MyMoneyMoney::FILE_4_BYTE_VALUE; MyMoneyMoney MyMoneyMoney::maxValue = MyMoneyMoney(INT64_MAX, 100); MyMoneyMoney MyMoneyMoney::minValue = MyMoneyMoney(INT64_MIN, 100); MyMoneyMoney MyMoneyMoney::autoCalc = MyMoneyMoney(INT64_MIN + 1, 100); void MyMoneyMoney::setNegativePrefixCurrencySymbol(const bool flag) { _negativePrefixCurrencySymbol = flag; } void MyMoneyMoney::setPositivePrefixCurrencySymbol(const bool flag) { _positivePrefixCurrencySymbol = flag; } void MyMoneyMoney::setNegativeMonetarySignPosition(const signPosition pos) { _negativeMonetarySignPosition = pos; } MyMoneyMoney::signPosition MyMoneyMoney::negativeMonetarySignPosition() { return _negativeMonetarySignPosition; } void MyMoneyMoney::setPositiveMonetarySignPosition(const signPosition pos) { _positiveMonetarySignPosition = pos; } MyMoneyMoney::signPosition MyMoneyMoney::positiveMonetarySignPosition() { return _positiveMonetarySignPosition; } void MyMoneyMoney::setThousandSeparator(const QChar &separator) { if (separator != ' ') _thousandSeparator = separator; else _thousandSeparator = 0; } const QChar MyMoneyMoney::thousandSeparator() { return _thousandSeparator; } void MyMoneyMoney::setDecimalSeparator(const QChar &separator) { if (separator != ' ') _decimalSeparator = separator; else _decimalSeparator = 0; } const QChar MyMoneyMoney::decimalSeparator() { return _decimalSeparator; } void MyMoneyMoney::setFileVersion(fileVersionE version) { _fileVersion = version; } MyMoneyMoney::MyMoneyMoney(const QString& pszAmount) : AlkValue(pszAmount, _decimalSeparator) { } MyMoneyMoney::~MyMoneyMoney() { } QString MyMoneyMoney::formatMoney(int denom, bool showThousandSeparator) const { return formatMoney("", denomToPrec(denom), showThousandSeparator); } QString MyMoneyMoney::formatMoney(const QString& currency, const int prec, bool showThousandSeparator) const { QString res; QString tmpCurrency = currency; int tmpPrec = prec; mpz_class denom = 1; mpz_class value; // if prec == -1 we want the maximum possible but w/o trailing zeroes if (tmpPrec > -1) { while (tmpPrec--) { denom *= 10; } } else { // fix it to a max of 9 digits on the right side for now denom = 1000000000; } // as long as AlkValue::convertDenominator() does not take an // mpz_class as the denominator, we need to use a signed int // and limit the precision to 9 digits (the max we can // present with 31 bits #if 1 signed int d; if (mpz_fits_sint_p(denom.get_mpz_t())) { d = mpz_get_si(denom.get_mpz_t()); } else { d = 1000000000; } value = static_cast(convertDenominator(d)).valueRef().get_num(); #else value = static_cast(convertDenominator(denom)).valueRef().get_num(); #endif // Once we really support multiple currencies then this method will // be much better than using KLocale::global()->formatMoney. bool bNegative = false; mpz_class left = value / static_cast(convertDenominator(d)).valueRef().get_den(); mpz_class right = mpz_class((valueRef() - mpq_class(left)) * denom); if (right < 0) { right = -right; bNegative = true; } if (left < 0) { left = -left; bNegative = true; } // convert the integer (left) part to a string res.append(left.get_str().c_str()); // if requested, insert thousand separators every three digits if (showThousandSeparator) { int pos = res.length(); while ((0 < (pos -= 3)) && thousandSeparator() != 0) res.insert(pos, thousandSeparator()); } // take care of the fractional part if (prec > 0 || (prec == -1 && right != 0)) { if (decimalSeparator() != 0) res += decimalSeparator(); QString rs = QString("%1").arg(right.get_str().c_str()); if (prec != -1) rs = rs.rightJustified(prec, '0', true); else { rs = rs.rightJustified(9, '0', true); // no trailing zeroes or decimal separators while (rs.endsWith('0')) rs.truncate(rs.length() - 1); while (rs.endsWith(QChar(decimalSeparator()))) rs.truncate(rs.length() - 1); } res += rs; } signPosition signpos = bNegative ? _negativeMonetarySignPosition : _positiveMonetarySignPosition; QString sign = bNegative ? "-" : ""; switch (signpos) { case ParensAround: res.prepend('('); res.append(')'); break; case BeforeQuantityMoney: res.prepend(sign); break; case AfterQuantityMoney: res.append(sign); break; case BeforeMoney: tmpCurrency.prepend(sign); break; case AfterMoney: tmpCurrency.append(sign); break; } if (!tmpCurrency.isEmpty()) { if (bNegative ? _negativePrefixCurrencySymbol : _positivePrefixCurrencySymbol) { res.prepend(' '); res.prepend(tmpCurrency); } else { res.append(' '); res.append(tmpCurrency); } } return res; } //////////////////////////////////////////////////////////////////////////////// // Name: operator+ // Purpose: Addition operator - adds the input amount to the object // Returns: The current object // Throws: Nothing. // Arguments: b - MyMoneyMoney object to be added // //////////////////////////////////////////////////////////////////////////////// const MyMoneyMoney MyMoneyMoney::operator+(const MyMoneyMoney& _b) const { - return AlkValue::operator+(_b); + return static_cast(AlkValue::operator+(_b)); } //////////////////////////////////////////////////////////////////////////////// // Name: operator- // Purpose: Addition operator - subtracts the input amount from the object // Returns: The current object // Throws: Nothing. // Arguments: AmountInPence - MyMoneyMoney object to be subtracted // //////////////////////////////////////////////////////////////////////////////// const MyMoneyMoney MyMoneyMoney::operator-(const MyMoneyMoney& _b) const { - return AlkValue::operator-(_b); + return static_cast(AlkValue::operator-(_b)); } //////////////////////////////////////////////////////////////////////////////// // Name: operator* // Purpose: Multiplication operator - multiplies the input amount to the object // Returns: The current object // Throws: Nothing. // Arguments: b - MyMoneyMoney object to be multiplied // //////////////////////////////////////////////////////////////////////////////// const MyMoneyMoney MyMoneyMoney::operator*(const MyMoneyMoney& _b) const { - return AlkValue::operator*(_b); + return static_cast(AlkValue::operator*(_b)); } //////////////////////////////////////////////////////////////////////////////// // Name: operator/ // Purpose: Division operator - divides the object by the input amount // Returns: The current object // Throws: Nothing. // Arguments: b - MyMoneyMoney object to be used as dividend // //////////////////////////////////////////////////////////////////////////////// const MyMoneyMoney MyMoneyMoney::operator/(const MyMoneyMoney& _b) const { - return AlkValue::operator/(_b); + return static_cast(AlkValue::operator/(_b)); } MyMoneyMoney MyMoneyMoney::convert(const signed64 _denom, const roundingMethod how) const { - return convertDenominator(_denom, static_cast(how)); + return static_cast(convertDenominator(_denom, static_cast(how))); } MyMoneyMoney MyMoneyMoney::reduce() const { MyMoneyMoney out(*this); out.canonicalize(); return out; } signed64 MyMoneyMoney::precToDenom(int prec) { signed64 denom = 1; while (prec--) denom *= 10; return denom; } double MyMoneyMoney::toDouble() const { return valueRef().get_d(); } int MyMoneyMoney::denomToPrec(signed64 fract) { int rc = 0; while (fract > 1) { rc++; fract /= 10; } return rc; } diff --git a/kmymoney/mymoney/mymoneymoney.h b/kmymoney/mymoney/mymoneymoney.h index 97668c4f3..e23ef43d7 100644 --- a/kmymoney/mymoney/mymoneymoney.h +++ b/kmymoney/mymoney/mymoneymoney.h @@ -1,469 +1,469 @@ /*************************************************************************** mymoneymoney.h ------------------- copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef MYMONEYMONEY_H #define MYMONEYMONEY_H // #include #include //FIXME workaround for dealing with lond double #include // So we can save this object #include #include #include #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" #include "mymoneyexception.h" #include typedef qint64 signed64; typedef quint64 unsigned64; /** * This class represents a value within the MyMoney Engine * * @author Michael Edwardes * @author Thomas Baumgart */ class KMM_MYMONEY_EXPORT MyMoneyMoney : public AlkValue { KMM_MYMONEY_UNIT_TESTABLE public: enum fileVersionE { FILE_4_BYTE_VALUE = 0, FILE_8_BYTE_VALUE }; enum signPosition { // keep those in sync with the ones defined in klocale.h ParensAround = 0, BeforeQuantityMoney = 1, AfterQuantityMoney = 2, BeforeMoney = 3, AfterMoney = 4 }; enum roundingMethod { RndNever = 0, RndFloor, RndCeil, RndTrunc, RndPromote, RndHalfDown, RndHalfUp, RndRound }; // construction MyMoneyMoney(); explicit MyMoneyMoney(const int iAmount, const signed64 denom); explicit MyMoneyMoney(const long int iAmount, const signed64 denom); explicit MyMoneyMoney(const QString& pszAmount); explicit MyMoneyMoney(const signed64 Amount, const signed64 denom); explicit MyMoneyMoney(const double dAmount, const signed64 denom = 100); // copy constructor MyMoneyMoney(const MyMoneyMoney& Amount); - MyMoneyMoney(const AlkValue& Amount); + explicit MyMoneyMoney(const AlkValue& Amount); virtual ~MyMoneyMoney(); const MyMoneyMoney abs() const { - return AlkValue::abs(); + return static_cast(AlkValue::abs()); }; /** * This method returns a formatted string according to the settings * of _thousandSeparator, _decimalSeparator, _negativeMonetarySignPosition, * _positiveMonetaryPosition, _negativePrefixCurrencySymbol and * _positivePrefixCurrencySymbol. Those values can be modified using * the appropriate set-methods. * * @param currency The currency symbol * @param prec The number of fractional digits * @param showThousandSeparator should the thousandSeparator symbol * be inserted (@a true) * or not (@a false) (default true) */ QString formatMoney(const QString& currency, const int prec, bool showThousandSeparator = true) const; /** * This is a convenience method. It behaves exactly as the above one, * but takes the information about precision out of the denomination * @a denom. No currency symbol is shown. If you want to add a currency * symbol, please use MyMoneyUtils::formatMoney(const MyMoneyAccount& acc, const MyMoneySecurity& sec, bool showThousandSeparator) * instead. * * @note denom is often set to account.fraction(security). */ QString formatMoney(int denom, bool showThousandSeparator = true) const; /** * This method is used to convert the smallest fraction information into * the corresponding number of digits used for precision. * * @param fract smallest fractional part (e.g. 100 for cents) * @return number of precision digits (e.g. 2 for cents) */ static int denomToPrec(signed64 fract); MyMoneyMoney convert(const signed64 denom = 100, const roundingMethod how = RndRound) const; static signed64 precToDenom(int prec); double toDouble() const; static void setThousandSeparator(const QChar &); static void setDecimalSeparator(const QChar &); static void setNegativeMonetarySignPosition(const signPosition pos); static void setPositiveMonetarySignPosition(const signPosition pos); static void setNegativePrefixCurrencySymbol(const bool flags); static void setPositivePrefixCurrencySymbol(const bool flags); static const QChar thousandSeparator(); static const QChar decimalSeparator(); static signPosition negativeMonetarySignPosition(); static signPosition positiveMonetarySignPosition(); static void setFileVersion(const fileVersionE version); const MyMoneyMoney& operator=(const QString& pszAmount); const MyMoneyMoney& operator=(const AlkValue& val); // comparison bool operator==(const MyMoneyMoney& Amount) const; bool operator!=(const MyMoneyMoney& Amount) const; bool operator<(const MyMoneyMoney& Amount) const; bool operator>(const MyMoneyMoney& Amount) const; bool operator<=(const MyMoneyMoney& Amount) const; bool operator>=(const MyMoneyMoney& Amount) const; bool operator==(const QString& pszAmount) const; bool operator!=(const QString& pszAmount) const; bool operator<(const QString& pszAmount) const; bool operator>(const QString& pszAmount) const; bool operator<=(const QString& pszAmount) const; bool operator>=(const QString& pszAmount) const; // calculation const MyMoneyMoney operator+(const MyMoneyMoney& Amount) const; const MyMoneyMoney operator-(const MyMoneyMoney& Amount) const; const MyMoneyMoney operator*(const MyMoneyMoney& factor) const; const MyMoneyMoney operator/(const MyMoneyMoney& Amount) const; const MyMoneyMoney operator-() const; const MyMoneyMoney operator*(int factor) const; static MyMoneyMoney maxValue; static MyMoneyMoney minValue; static MyMoneyMoney autoCalc; bool isNegative() const { return (valueRef() < 0) ? true : false; } bool isPositive() const { return (valueRef() > 0) ? true : false; } bool isZero() const { return valueRef() == 0; } bool isAutoCalc() const { return (*this == autoCalc); } MyMoneyMoney reduce() const; static const MyMoneyMoney ONE; static const MyMoneyMoney MINUS_ONE; private: static QChar _thousandSeparator; static QChar _decimalSeparator; static signPosition _negativeMonetarySignPosition; static signPosition _positiveMonetarySignPosition; static bool _negativePrefixCurrencySymbol; static bool _positivePrefixCurrencySymbol; static MyMoneyMoney::fileVersionE _fileVersion; }; //============================================================================= // // Inline functions // //============================================================================= //////////////////////////////////////////////////////////////////////////////// // Name: MyMoneyMoney // Purpose: Constructor - constructs object set to 0. // Returns: None // Throws: Nothing. // Arguments: None // //////////////////////////////////////////////////////////////////////////////// inline MyMoneyMoney::MyMoneyMoney() : AlkValue() { } //////////////////////////////////////////////////////////////////////////////// // Name: MyMoneyMoney // Purpose: Constructor - constructs object from an amount in a signed64 value // Returns: None // Throws: Nothing. // Arguments: Amount - signed 64 object containing amount // denom - denominator of the object // //////////////////////////////////////////////////////////////////////////////// inline MyMoneyMoney::MyMoneyMoney(signed64 Amount, const signed64 denom) { if (!denom) throw MYMONEYEXCEPTION("Denominator 0 not allowed!"); *this = AlkValue(QString("%1/%2").arg(Amount).arg(denom), _decimalSeparator); } //////////////////////////////////////////////////////////////////////////////// // Name: MyMoneyMoney // Purpose: Constructor - constructs object from an amount in a double value // Returns: None // Throws: Nothing. // Arguments: dAmount - double object containing amount // denom - denominator of the object // //////////////////////////////////////////////////////////////////////////////// inline MyMoneyMoney::MyMoneyMoney(const double dAmount, const signed64 denom) : AlkValue(dAmount, denom) { } //////////////////////////////////////////////////////////////////////////////// // Name: MyMoneyMoney // Purpose: Constructor - constructs object from an amount in a integer value // Returns: None // Throws: Nothing. // Arguments: iAmount - integer object containing amount // denom - denominator of the object // //////////////////////////////////////////////////////////////////////////////// inline MyMoneyMoney::MyMoneyMoney(const int iAmount, const signed64 denom) { if (!denom) throw MYMONEYEXCEPTION("Denominator 0 not allowed!"); *this = AlkValue(iAmount, denom); } //////////////////////////////////////////////////////////////////////////////// // Name: MyMoneyMoney // Purpose: Constructor - constructs object from an amount in a long integer value // Returns: None // Throws: Nothing. // Arguments: iAmount - integer object containing amount // denom - denominator of the object // //////////////////////////////////////////////////////////////////////////////// inline MyMoneyMoney::MyMoneyMoney(const long int iAmount, const signed64 denom) { if (!denom) throw MYMONEYEXCEPTION("Denominator 0 not allowed!"); *this = AlkValue(QString("%1/%2").arg(iAmount).arg(denom), _decimalSeparator); } //////////////////////////////////////////////////////////////////////////////// // Name: MyMoneyMoney // Purpose: Copy Constructor - constructs object from another // MyMoneyMoney object // Returns: None // Throws: Nothing. // Arguments: Amount - MyMoneyMoney object to be copied // //////////////////////////////////////////////////////////////////////////////// inline MyMoneyMoney::MyMoneyMoney(const MyMoneyMoney& Amount) : AlkValue(Amount) { } inline MyMoneyMoney::MyMoneyMoney(const AlkValue& Amount) : AlkValue(Amount) { } inline const MyMoneyMoney& MyMoneyMoney::operator=(const AlkValue & val) { AlkValue::operator=(val); return *this; } //////////////////////////////////////////////////////////////////////////////// // Name: operator= // Purpose: Assignment operator - modifies object from input NULL terminated // string // Returns: Const reference to the object // Throws: Nothing. // Arguments: pszAmount - NULL terminated string that contains amount // //////////////////////////////////////////////////////////////////////////////// inline const MyMoneyMoney& MyMoneyMoney::operator=(const QString & pszAmount) { AlkValue::operator=(pszAmount); return *this; } //////////////////////////////////////////////////////////////////////////////// // Name: operator== // Purpose: Compare equal operator - compares object with input MyMoneyMoney object // Returns: true if equal, otherwise false // Throws: Nothing. // Arguments: Amount - MyMoneyMoney object to be compared with // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator==(const MyMoneyMoney& Amount) const { return AlkValue::operator==(Amount); } //////////////////////////////////////////////////////////////////////////////// // Name: operator!= // Purpose: Compare not equal operator - compares object with input MyMoneyMoney object // Returns: true if not equal, otherwise false // Throws: Nothing. // Arguments: Amount - MyMoneyMoney object to be compared with // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator!=(const MyMoneyMoney& Amount) const { return AlkValue::operator!=(Amount); } //////////////////////////////////////////////////////////////////////////////// // Name: operator< // Purpose: Compare less than operator - compares object with input MyMoneyMoney object // Returns: true if object less than input amount // Throws: Nothing. // Arguments: Amount - MyMoneyMoney object to be compared with // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator<(const MyMoneyMoney& Amount) const { return AlkValue::operator<(Amount); } //////////////////////////////////////////////////////////////////////////////// // Name: operator> // Purpose: Compare greater than operator - compares object with input MyMoneyMoney // object // Returns: true if object greater than input amount // Throws: Nothing. // Arguments: Amount - MyMoneyMoney object to be compared with // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator>(const MyMoneyMoney& Amount) const { return AlkValue::operator>(Amount); } //////////////////////////////////////////////////////////////////////////////// // Name: operator<= // Purpose: Compare less than equal to operator - compares object with input // MyMoneyMoney object // Returns: true if object less than or equal to input amount // Throws: Nothing. // Arguments: Amount - MyMoneyMoney object to be compared with // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator<=(const MyMoneyMoney& Amount) const { return AlkValue::operator<=(Amount); } //////////////////////////////////////////////////////////////////////////////// // Name: operator>= // Purpose: Compare greater than equal to operator - compares object with input // MyMoneyMoney object // Returns: true if object greater than or equal to input amount // Throws: Nothing. // Arguments: Amount - MyMoneyMoney object to be compared with // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator>=(const MyMoneyMoney& Amount) const { return AlkValue::operator>=(Amount); } //////////////////////////////////////////////////////////////////////////////// // Name: operator== // Purpose: Compare equal operator - compares object with input amount in a // NULL terminated string // Returns: true if equal, otherwise false // Throws: Nothing. // Arguments: pszAmount - NULL terminated string that contains amount // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator==(const QString& pszAmount) const { return *this == MyMoneyMoney(pszAmount); } //////////////////////////////////////////////////////////////////////////////// // Name: operator!= // Purpose: Compare not equal operator - compares object with input amount in // a NULL terminated string // Returns: true if not equal, otherwise false // Throws: Nothing. // Arguments: pszAmount - NULL terminated string that contains amount // //////////////////////////////////////////////////////////////////////////////// inline bool MyMoneyMoney::operator!=(const QString& pszAmount) const { return ! operator==(pszAmount) ; } //////////////////////////////////////////////////////////////////////////////// // Name: operator- // Purpose: Unary operator - returns the negative value from the object // Returns: The current object // Throws: Nothing. // Arguments: None // //////////////////////////////////////////////////////////////////////////////// inline const MyMoneyMoney MyMoneyMoney::operator-() const { - return AlkValue::operator-(); + return static_cast(AlkValue::operator-()); } //////////////////////////////////////////////////////////////////////////////// // Name: operator* // Purpose: Multiplication operator - multiplies the object with factor // Returns: The current object // Throws: Nothing. // Arguments: AmountInPence - long object to be multiplied // //////////////////////////////////////////////////////////////////////////////// inline const MyMoneyMoney MyMoneyMoney::operator*(int factor) const { - return AlkValue::operator*(factor); + return static_cast(AlkValue::operator*(factor)); } /** * Make it possible to hold @ref MyMoneyMoney objects * inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyMoney) #endif diff --git a/kmymoney/mymoney/onlinejobfolder.h b/kmymoney/mymoney/onlinejobfolder.h index 710b20e44..0067e1b17 100644 --- a/kmymoney/mymoney/onlinejobfolder.h +++ b/kmymoney/mymoney/onlinejobfolder.h @@ -1,65 +1,65 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2013 Christian Dávid * * 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 ONLINEJOBFOLDER_H #define ONLINEJOBFOLDER_H /** * @brief Folder to organize @ref onlineJob "onlineJobs" * * This class is mainly for forward compatibility. At the monent there are only four default * folders outbox(), drafts(), templates(), historic(). These static methods are also the only * way to create a folder. * * If job organizing becomes more complicated this class can be extended. * */ class onlineJobFolder { public: - inline onlineJobFolder(const onlineJobFolder &other) + inline onlineJobFolder(const onlineJobFolder &other) // krazy:exclude=explicit : m_folder(other.m_folder) {} static onlineJobFolder outbox() { return onlineJobFolder(folderOutbox); } static onlineJobFolder drafts() { return onlineJobFolder(folderDrafts); } static onlineJobFolder templates() { return onlineJobFolder(folderTemplates); } static onlineJobFolder historic() { return onlineJobFolder(folderHistoric); } private: enum onlineJobFolders { folderOutbox, folderDrafts, folderTemplates, folderHistoric }; onlineJobFolder(); - onlineJobFolder(const onlineJobFolders& folder); + explicit onlineJobFolder(const onlineJobFolders& folder); onlineJobFolders m_folder; }; #endif // ONLINEJOBFOLDER_H diff --git a/kmymoney/mymoney/payeeidentifier/payeeidentifier.h b/kmymoney/mymoney/payeeidentifier/payeeidentifier.h index e457d38f8..095bf2409 100644 --- a/kmymoney/mymoney/payeeidentifier/payeeidentifier.h +++ b/kmymoney/mymoney/payeeidentifier/payeeidentifier.h @@ -1,177 +1,177 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 PAYEEIDENTIFIER_H #define PAYEEIDENTIFIER_H #include #include /** @todo fix include path after upgrade to cmake 3 */ #include "payeeidentifier/kmm_payeeidentifier_export.h" // Q_DECLARE_METATYPE requries this include class QDomDocument; class QDomElement; class payeeIdentifierData; class KMM_PAYEEIDENTIFIER_EXPORT payeeIdentifier { public: typedef unsigned int id_t; explicit payeeIdentifier(); explicit payeeIdentifier(payeeIdentifierData *const data); explicit payeeIdentifier(const id_t& id, payeeIdentifierData *const data); explicit payeeIdentifier(const QString& id, payeeIdentifierData *const data); explicit payeeIdentifier(const id_t& id, const payeeIdentifier& other); payeeIdentifier(const payeeIdentifier& other); ~payeeIdentifier(); payeeIdentifier& operator=(const payeeIdentifier& other); bool operator==(const payeeIdentifier& other); /** @brief Check if any data is associated */ bool isNull() const { return (m_payeeIdentifier == 0); } /** * @brief create xml to save this payeeIdentifier * * It creates a new element below parent which is used to store all data. * * The counter part to load a payee identifier again is payeeIdentifierLoader::createPayeeIdentifierFromXML(). */ void writeXML(QDomDocument &document, QDomElement &parent, const QString& elementName = QLatin1String("payeeIdentifier")) const; /** * @throws payeeIdentifier::empty */ payeeIdentifierData* operator->(); /** @copydoc operator->() */ const payeeIdentifierData* operator->() const; /** @copydoc operator->() */ payeeIdentifierData* data(); /** @copydoc operator->() */ const payeeIdentifierData* data() const; template< class T > T* data(); template< class T > const T* data() const; bool isValid() const; id_t id() const { return m_id; } QString idString() const; void clearId() { m_id = 0; } /** * @brief Get payeeIdentifier Iid which identifiers the type * * @return An payeeIdentifier id or QString() if no data is associated */ QString iid() const; /** * @brief Base for exceptions thrown by payeeIdentifier * * @internal Using MyMoneyException instead is not possible because * it would lead to cyclic inter-target dependenies. We could create a new * shared library which includes MyMoneyException only, but this could be over- * powered. */ class exception {}; /** * @brief Thrown if a cast of a payeeIdentifier fails * * This is inspired by std::bad_cast * @todo inherit from MyMoneyException */ class badCast : public exception { public: - badCast(const QString& file = "", const long unsigned int& line = 0) + explicit badCast(const QString& file = "", const long unsigned int& line = 0) //: MyMoneyException("Casted payeeIdentifier with wrong type", file, line) { Q_UNUSED(file); Q_UNUSED(line); } }; /** * @brief Thrown if one tried to access the data of a null payeeIdentifier * @todo inherit from MyMoneyException */ class empty : public exception { public: - empty(const QString& file = "", const long unsigned int& line = 0) + explicit empty(const QString& file = "", const long unsigned int& line = 0) //: MyMoneyException("Requested payeeIdentifierData of empty payeeIdentifier", file, line) { Q_UNUSED(file); Q_UNUSED(line); } }; private: /** * The id is only used in MyMoneyPayeeIdentifierContainer at the moment. */ id_t m_id; // Must access the id, but the id should not be used outside of that class at the moment friend class MyMoneyPayeeIdentifierContainer; friend class payeeIdentifierLoader; payeeIdentifierData* m_payeeIdentifier; }; template T* payeeIdentifier::data() { T *const ident = dynamic_cast(operator->()); if (ident == 0) throw badCast(__FILE__, __LINE__); return ident; } template const T* payeeIdentifier::data() const { const T *const ident = dynamic_cast(operator->()); if (ident == 0) throw badCast(__FILE__, __LINE__); return ident; } Q_DECLARE_METATYPE(payeeIdentifier) #endif // PAYEEIDENTIFIER_H diff --git a/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.h b/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.h index 853c4272c..29ff4a754 100644 --- a/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.h +++ b/kmymoney/mymoney/payeeidentifier/payeeidentifiercontainermodel.h @@ -1,84 +1,84 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 PAYEEIDENTIFIERCONTAINERMODEL_H #define PAYEEIDENTIFIERCONTAINERMODEL_H #include #include "mymoney/mymoneypayeeidentifiercontainer.h" #include "payeeidentifier/payeeidentifier.h" /** * @brief Model for MyMoneyPayeeIdentifierContainer * * Changes the user does have initernal effect only. */ class payeeIdentifierContainerModel : public QAbstractListModel { Q_OBJECT public: enum roles { payeeIdentifierType = Qt::UserRole, /**< type of payeeIdentifier */ payeeIdentifier = Qt::UserRole + 1 /**< actual payeeIdentifier */ }; - payeeIdentifierContainerModel(QObject* parent = 0); + explicit payeeIdentifierContainerModel(QObject* parent = 0); virtual QVariant data(const QModelIndex& index, int role) const; /** * This model only supports to edit payeeIdentifier role with a QVariant of type * payeeIdentifier. */ virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); virtual Qt::ItemFlags flags(const QModelIndex& index) const; virtual int rowCount(const QModelIndex& parent) const; virtual bool insertRows(int row, int count, const QModelIndex& parent); virtual bool removeRows(int row, int count, const QModelIndex& parent); /** * @brief Set source of data * * This makes the model editable. */ void setSource(MyMoneyPayeeIdentifierContainer data); /** @brief Get stored data */ QList< ::payeeIdentifier > identifiers() const; public slots: /** * @brief Removes all data from the model * * The model is not editable afterwards. */ void closeSource(); private: /** @internal * The use of a shared pointer makes this future prof. Because using identifier() causes * some unnecessary work. */ QSharedPointer m_data; }; #endif // PAYEEIDENTIFIERMODEL_H diff --git a/kmymoney/mymoney/storage/mymoneystoragesql.h b/kmymoney/mymoney/storage/mymoneystoragesql.h index 4ca072f9c..c8613bcae 100644 --- a/kmymoney/mymoney/storage/mymoneystoragesql.h +++ b/kmymoney/mymoney/storage/mymoneystoragesql.h @@ -1,618 +1,618 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2005 Tony Bloomfield * Copyright (C) Fernando Vilas * Copyright (C) 2014 Christian Dávid * * 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 MYMONEYSTORAGESQL_H #define MYMONEYSTORAGESQL_H #include #include #include #include #include #include #include #include #include #include #include class QIODevice; #include "imymoneystorageformat.h" #include "mymoneyprice.h" #include "mymoneyreport.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneydbdef.h" #include "mymoneydbdriver.h" #include "onlinejob.h" #include "payeeidentifier/payeeidentifier.h" // This is a convenience functor to make it easier to use STL algorithms // It will return false if the MyMoneyTransaction DOES match the filter. // This functor may disappear when all filtering can be handled in SQL. template struct QPair; class IMyMoneyStorage; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyCostCenter; class MyMoneyMoney; class MyMoneySchedule; class MyMoneyPayee; class MyMoneyTag; class MyMoneySplit; class MyMoneyTransaction; class MyMoneyBudget; class databaseStoreableObject; namespace eMyMoney { namespace TransactionFilter { enum class State; } } class FilterFail { public: - FilterFail(const MyMoneyTransactionFilter& filter) : m_filter(filter) {} + explicit FilterFail(const MyMoneyTransactionFilter& filter) : m_filter(filter) {} inline bool operator()(const QPair& transactionPair) { return (*this)(transactionPair.second); } inline bool operator()(const MyMoneyTransaction& transaction) { return (! m_filter.match(transaction)) && (m_filter.matchingSplits().count() == 0); } private: MyMoneyTransactionFilter m_filter; }; class MyMoneyStorageSql; //***************************************************************************** // Create a class to handle db transactions using scope // // Don't let the database object get destroyed while this object exists, // that would result in undefined behavior. class MyMoneyDbTransaction { public: MyMoneyDbTransaction(MyMoneyStorageSql& db, const QString& name); ~MyMoneyDbTransaction(); private: MyMoneyStorageSql& m_db; QString m_name; }; /** * The MyMoneySqlQuery class is derived from QSqlQuery to provide * a way to adjust some queries based on database type and make * debugging easier by providing a place to put debug statements. */ class MyMoneySqlQuery : public QSqlQuery { public: - MyMoneySqlQuery(MyMoneyStorageSql* db = 0); + explicit MyMoneySqlQuery(MyMoneyStorageSql* db = 0); virtual ~MyMoneySqlQuery(); bool exec(); bool exec(const QString & query); bool prepare(const QString & query); }; class IMyMoneySerialize; /** * The MyMoneyDbColumn class is a base type for generic db columns. * Derived types exist for several common column types. * * @todo Remove unneeded columns which store the row count of tables from kmmFileInfo */ class MyMoneyStorageSql : public IMyMoneyStorageFormat, public QSqlDatabase, public QSharedData { friend class MyMoneyDbDef; KMM_MYMONEY_UNIT_TESTABLE public: explicit MyMoneyStorageSql(IMyMoneySerialize *storage, const QUrl& = QUrl()); virtual ~MyMoneyStorageSql(); unsigned int currentVersion() const { return (m_db.currentVersion()); }; /** * MyMoneyStorageSql - open database file * * @param url pseudo-URL of database to be opened * @param openMode open mode, same as for QFile::open * @param clear whether existing data can be deleted * @return 0 - database successfully opened * @return 1 - database not opened, use lastError function for reason * @return -1 - output database not opened, contains data, clean not specified * */ int open(const QUrl &url, int openMode, bool clear = false); /** * MyMoneyStorageSql close the database * * @return void * */ void close(bool logoff = true); /** * MyMoneyStorageSql read all the database into storage * * @return void * */ bool readFile(); /** * MyMoneyStorageSql write/update the database from storage * * @return void * */ bool writeFile(); /** * MyMoneyStorageSql generalized error routine * * @return : error message to be displayed * */ const QString& lastError() const { return (m_error); }; /** * MyMoneyStorageSql get highest ID number from the database * * @return : highest ID number */ long unsigned highestNumberFromIdString(QString tableName, QString tableField, int prefixLength); /** * This method is used when a database file is open, and the data is to * be saved in a different file or format. It will ensure that all data * from the database is available in memory to enable it to be written. */ virtual void fillStorage(); /** * The following functions correspond to the identically named (usually) functions * within the Storage Manager, and are called to update the database */ void modifyUserInfo(const MyMoneyPayee& payee); void addInstitution(const MyMoneyInstitution& inst); void modifyInstitution(const MyMoneyInstitution& inst); void removeInstitution(const MyMoneyInstitution& inst); void addPayee(const MyMoneyPayee& payee); void modifyPayee(MyMoneyPayee payee); void removePayee(const MyMoneyPayee& payee); void addTag(const MyMoneyTag& tag); void modifyTag(const MyMoneyTag& tag); void removeTag(const MyMoneyTag& tag); void addAccount(const MyMoneyAccount& acc); void modifyAccount(const MyMoneyAccount& acc); void removeAccount(const MyMoneyAccount& acc); void addTransaction(const MyMoneyTransaction& tx); void modifyTransaction(const MyMoneyTransaction& tx); void removeTransaction(const MyMoneyTransaction& tx); void addSchedule(const MyMoneySchedule& sch); void modifySchedule(const MyMoneySchedule& sch); void removeSchedule(const MyMoneySchedule& sch); void addSecurity(const MyMoneySecurity& sec); void modifySecurity(const MyMoneySecurity& sec); void removeSecurity(const MyMoneySecurity& sec); void addPrice(const MyMoneyPrice& p); void removePrice(const MyMoneyPrice& p); void addCurrency(const MyMoneySecurity& sec); void modifyCurrency(const MyMoneySecurity& sec); void removeCurrency(const MyMoneySecurity& sec); void addReport(const MyMoneyReport& rep); void modifyReport(const MyMoneyReport& rep); void removeReport(const MyMoneyReport& rep); void addBudget(const MyMoneyBudget& bud); void modifyBudget(const MyMoneyBudget& bud); void removeBudget(const MyMoneyBudget& bud); void addOnlineJob(const onlineJob& job); void modifyOnlineJob(const onlineJob& job); void removeOnlineJob(const onlineJob& job); void addPayeeIdentifier(payeeIdentifier& ident); void modifyPayeeIdentifier(const payeeIdentifier& ident); void removePayeeIdentifier(const payeeIdentifier& ident); unsigned long transactionCount(const QString& aid = QString()) const; inline const QHash transactionCountMap() const { return (m_transactionCountMap); }; /** * The following functions are perform the same operations as the * above functions, but on a QList of the items. * This reduces db round-trips, so should be the preferred method when * such a function exists. */ void modifyAccountList(const QList& acc); /** * the storage manager also needs the following read entry points */ const QMap fetchAccounts(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchBalance(const QStringList& id, const QDate& date) const; const QMap fetchBudgets(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchCurrencies(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchInstitutions(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchPayees(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchTags(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchOnlineJobs(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchCostCenters(const QStringList& idList = QStringList(), bool forUpdate = false) const; const MyMoneyPriceList fetchPrices(const QStringList& fromIdList = QStringList(), const QStringList& toIdList = QStringList(), bool forUpdate = false) const; MyMoneyPrice fetchSinglePrice(const QString& fromId, const QString& toId, const QDate& date_, bool exactDate, bool = false) const; const QMap fetchReports(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchSchedules(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchSecurities(const QStringList& idList = QStringList(), bool forUpdate = false) const; const QMap fetchTransactions(const QString& tidList = QString(), const QString& dateClause = QString(), bool forUpdate = false) const; const QMap fetchTransactions(const MyMoneyTransactionFilter& filter) const; payeeIdentifier fetchPayeeIdentifier(const QString& id) const; const QMap fetchPayeeIdentifiers(const QStringList& idList = QStringList()) const; bool isReferencedByTransaction(const QString& id) const; void readPayees(const QString&); void readPayees(const QList& payeeList = QList()); void readTags(const QString&); void readTags(const QList& tagList = QList()); void readTransactions(const MyMoneyTransactionFilter& filter); void setProgressCallback(void(*callback)(int, int, const QString&)); virtual void readFile(QIODevice* s, IMyMoneySerialize* storage) { Q_UNUSED(s); Q_UNUSED(storage) }; virtual void writeFile(QIODevice* s, IMyMoneySerialize* storage) { Q_UNUSED(s); Q_UNUSED(storage) }; void startCommitUnit(const QString& callingFunction); bool endCommitUnit(const QString& callingFunction); void cancelCommitUnit(const QString& callingFunction); long unsigned getRecCount(const QString& table) const; long unsigned getNextBudgetId() const; long unsigned getNextAccountId() const; long unsigned getNextInstitutionId() const; long unsigned getNextPayeeId() const; long unsigned getNextTagId() const; long unsigned getNextOnlineJobId() const; long unsigned getNextPayeeIdentifierId() const; long unsigned getNextReportId() const; long unsigned getNextScheduleId() const; long unsigned getNextSecurityId() const; long unsigned getNextTransactionId() const; long unsigned getNextCostCenterId() const; long unsigned incrementBudgetId(); long unsigned incrementAccountId(); long unsigned incrementInstitutionId(); long unsigned incrementPayeeId(); long unsigned incrementTagId(); long unsigned incrementReportId(); long unsigned incrementScheduleId(); long unsigned incrementSecurityId(); long unsigned incrementTransactionId(); long unsigned incrementOnlineJobId(); long unsigned incrementPayeeIdentfierId(); long unsigned incrementCostCenterId(); void loadAccountId(const unsigned long& id); void loadTransactionId(const unsigned long& id); void loadPayeeId(const unsigned long& id); void loadTagId(const unsigned long& id); void loadInstitutionId(const unsigned long& id); void loadScheduleId(const unsigned long& id); void loadSecurityId(const unsigned long& id); void loadReportId(const unsigned long& id); void loadBudgetId(const unsigned long& id); void loadOnlineJobId(const unsigned long& id); void loadPayeeIdentifierId(const unsigned long& id); void loadCostCenterId(const unsigned long& id); /** * This method allows to modify the precision with which prices * are handled within the object. The default of the precision is 4. */ static void setPrecision(int prec); /** * This method allows to modify the start date for transaction retrieval * The default of the precision is Jan 1st, 1900. */ static void setStartDate(const QDate &startDate); private: bool fileExists(const QString& dbName); /** @brief a function to build a comprehensive error message for an SQL error */ QString& buildError(const QSqlQuery& q, const QString& function, const QString& message) const; /** @copydoc buildError */ QString& buildError(const QSqlQuery& q, const QString& function, const QString& message, const QSqlDatabase*) const; /** * @name writeFromStorageMethods * @{ * These method write all data from m_storage to the database. Data which is * stored in the database is deleted. */ void writeUserInformation(); void writeInstitutions(); void writePayees(); void writeTags(); void writeAccounts(); void writeTransactions(); void writeSchedules(); void writeSecurities(); void writePrices(); void writeCurrencies(); void writeFileInfo(); void writeReports(); void writeBudgets(); void writeOnlineJobs(); /** @} */ /** * @name writeMethods * @{ * These methods bind the data fields of MyMoneyObjects to a given query and execute the query. * This is helpfull as the query has usually an update and a insert format. */ void writeInstitutionList(const QList& iList, QSqlQuery& q); void writePayee(const MyMoneyPayee& p, QSqlQuery& q, bool isUserInfo = false); void writeTag(const MyMoneyTag& p, QSqlQuery& q); void writeAccountList(const QList& accList, QSqlQuery& q); void writeTransaction(const QString& txId, const MyMoneyTransaction& tx, QSqlQuery& q, const QString& type); void writeSplits(const QString& txId, const QString& type, const QList& splitList); void writeTagSplitsList(const QString& txId, const QList& splitList, const QList& splitIdList); void writeSplitList(const QString& txId, const QList& splitList, const QString& type, const QList& splitIdList, QSqlQuery& q); void writeSchedule(const MyMoneySchedule& sch, QSqlQuery& q, bool insert); void writeSecurity(const MyMoneySecurity& security, QSqlQuery& q); void writePricePair(const MyMoneyPriceEntries& p); void writePrice(const MyMoneyPrice& p); void writeCurrency(const MyMoneySecurity& currency, QSqlQuery& q); void writeReport(const MyMoneyReport& rep, QSqlQuery& q); void writeBudget(const MyMoneyBudget& bud, QSqlQuery& q); void writeKeyValuePairs(const QString& kvpType, const QVariantList& kvpId, const QList >& pairs); void writeOnlineJob(const onlineJob& job, QSqlQuery& query); void writePayeeIdentifier(const payeeIdentifier& pid, QSqlQuery& query); /** @} */ /** * @name readMethods * @{ */ void readFileInfo(); void readLogonData(); void readUserInformation(); void readInstitutions(); void readAccounts(); void readTransactions(const QString& tidList = QString(), const QString& dateClause = QString()); void readSplit(MyMoneySplit& s, const QSqlQuery& q) const; const MyMoneyKeyValueContainer readKeyValuePairs(const QString& kvpType, const QString& kvpId) const; const QHash readKeyValuePairs(const QString& kvpType, const QStringList& kvpIdList) const; void readSchedules(); void readSecurities(); void readPrices(); void readCurrencies(); void readReports(); void readBudgets(); /** @} */ template long unsigned int getNextId(const QString& table, const QString& id, const int prefixLength) const; void deleteTransaction(const QString& id); void deleteTagSplitsList(const QString& txId, const QList& splitIdList); void deleteSchedule(const QString& id); void deleteKeyValuePairs(const QString& kvpType, const QVariantList& kvpId); long unsigned calcHighId(const long unsigned&, const QString&); void setVersion(const QString& version); void signalProgress(int current, int total, const QString& = "") const; void (*m_progressCallback)(int, int, const QString&); //void startCommitUnit (const QString& callingFunction); //void endCommitUnit (const QString& callingFunction); //void cancelCommitUnit (const QString& callingFunction); int splitState(const eMyMoney::TransactionFilter::State& state) const; inline const QDate getDate(const QString& date) const { return (date.isNull() ? QDate() : QDate::fromString(date, Qt::ISODate)); } inline const QDateTime getDateTime(const QString& date) const { return (date.isNull() ? QDateTime() : QDateTime::fromString(date, Qt::ISODate)); } // open routines /** * MyMoneyStorageSql create database * * @param url pseudo-URL of database to be opened * * @return true - creation successful * @return false - could not create * */ bool createDatabase(const QUrl &url); int upgradeDb(); int upgradeToV1(); int upgradeToV2(); int upgradeToV3(); int upgradeToV4(); int upgradeToV5(); int upgradeToV6(); int upgradeToV7(); int upgradeToV8(); int upgradeToV9(); int upgradeToV10(); int upgradeToV11(); int upgradeToV12(); int createTables(); void createTable(const MyMoneyDbTable& t, int version = std::numeric_limits::max()); bool alterTable(const MyMoneyDbTable& t, int fromVersion); void clean(); int isEmpty(); // for bug 252841 const QStringList tables(QSql::TableType tt) { return (m_driver->tables(tt, static_cast(*this))); }; //! Returns 1 in case the @a column exists in @a table, 0 if not. In case of error, -1 is returned. int haveColumnInTable(const QString& table, const QString& column); /** * @brief Ensure the storagePlugin with iid was setup * * @throws MyMoneyException in case of an error which makes the use * of the plugin unavailable. */ bool setupStoragePlugin(QString iid); void insertStorableObject(const databaseStoreableObject& obj, const QString& id); void updateStorableObject(const databaseStoreableObject& obj, const QString& id); void deleteStorableObject(const databaseStoreableObject& obj, const QString& id); // data QExplicitlySharedDataPointer m_driver; MyMoneyDbDef m_db; unsigned int m_dbVersion; IMyMoneySerialize *m_storage; IMyMoneyStorage *m_storagePtr; // input options bool m_loadAll; // preload all data bool m_override; // override open if already in use // error message QString m_error; // record counts long unsigned m_institutions; long unsigned m_accounts; long unsigned m_payees; long unsigned m_tags; long unsigned m_transactions; long unsigned m_splits; long unsigned m_securities; long unsigned m_prices; long unsigned m_currencies; long unsigned m_schedules; long unsigned m_reports; long unsigned m_kvps; long unsigned m_budgets; long unsigned m_onlineJobs; long unsigned m_payeeIdentifier; // Cache for next id to use // value 0 means data is not available and has to be loaded from the database long unsigned m_hiIdInstitutions; long unsigned m_hiIdPayees; long unsigned m_hiIdTags; long unsigned m_hiIdAccounts; long unsigned m_hiIdTransactions; long unsigned m_hiIdSchedules; long unsigned m_hiIdSecurities; long unsigned m_hiIdReports; long unsigned m_hiIdBudgets; long unsigned m_hiIdOnlineJobs; long unsigned m_hiIdPayeeIdentifier; long unsigned m_hiIdCostCenter; // encrypt option - usage TBD QString m_encryptData; /** * This variable is used to suppress status messages except during * initial data load and final write */ bool m_displayStatus; void alert(QString s) const { qDebug() << s; }; // FIXME: remove... /** The following keeps track of commitment units (known as transactions in SQL * though it would be confusing to use that term within KMM). It is implemented * as a stack for debug purposes. Long term, probably a count would suffice */ QStack m_commitUnitStack; /** * This member variable is used to preload transactions for preferred accounts */ MyMoneyTransactionFilter m_preferred; /** * This member variable is used because reading prices from a file uses the 'add...' function rather than a * 'load...' function which other objects use. Having this variable allows us to avoid needing to check the * database to see if this really is a new or modified price */ bool m_readingPrices; /** * This member variable holds a map of transaction counts per account, indexed by * the account id. It is used * to avoid having to scan all transactions whenever a count is needed. It should * probably be moved into the MyMoneyAccount object; maybe we will do that once * the database code has been properly checked out */ QHash m_transactionCountMap; /** * These member variables hold the user name and date/time of logon */ QString m_logonUser; QDateTime m_logonAt; QDate m_txPostDate; // FIXME: remove when Tom puts date into split object //Disable copying MyMoneyStorageSql(const MyMoneyStorageSql& rhs); MyMoneyStorageSql& operator= (const MyMoneyStorageSql& rhs); bool m_newDatabase; /** * This member keeps the current precision to be used fro prices. * @sa setPrecision() */ static int m_precision; /** * This member keeps the current start date used for transaction retrieval. * @sa setStartDate() */ static QDate m_startDate; /** * */ QSet m_loadedStoragePlugins; }; #endif // MYMONEYSTORAGESQL_H diff --git a/kmymoney/mymoney/tests/mymoneymoney-test.cpp b/kmymoney/mymoney/tests/mymoneymoney-test.cpp index eda329f02..4ae6c7ae1 100644 --- a/kmymoney/mymoney/tests/mymoneymoney-test.cpp +++ b/kmymoney/mymoney/tests/mymoneymoney-test.cpp @@ -1,707 +1,707 @@ /*************************************************************************** mymoneymoneytest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "mymoneymoney-test.h" #include #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyMoneyTest; #include #include "mymoneyexception.h" #include "mymoneymoney.h" QTEST_GUILESS_MAIN(MyMoneyMoneyTest) void MyMoneyMoneyTest::init() { m_0 = new MyMoneyMoney(12, 100); m_1 = new MyMoneyMoney(-10, 100); m_2 = new MyMoneyMoney(2, 100); m_3 = new MyMoneyMoney(123, 1); m_4 = new MyMoneyMoney(1234, 1000); m_5 = new MyMoneyMoney(195883, 100000); m_6 = new MyMoneyMoney(1.247658435, 1000000000); MyMoneyMoney::setDecimalSeparator('.'); MyMoneyMoney::setThousandSeparator(','); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney); } void MyMoneyMoneyTest::cleanup() { delete m_0; delete m_1; delete m_2; delete m_3; delete m_4; delete m_5; delete m_6; } void MyMoneyMoneyTest::testEmptyConstructor() { //qDebug("testing %s", qPrintable(m_0->toString())); MyMoneyMoney *m = new MyMoneyMoney(); QVERIFY(m->valueRef() == 0); QVERIFY(m->toString() == QString("0/1")); QVERIFY(m->valueRef().get_den() == 1); delete m; } void MyMoneyMoneyTest::testIntConstructor() { //qDebug("Current value: %s",qPrintable( m_0->toString()) ); //QVERIFY(m_0->valueRef().get_num() == 12); //QVERIFY(m_0->valueRef().get_den() == 100); QVERIFY(m_0->valueRef().get_num() == 3); QVERIFY(m_0->valueRef().get_den() == 25); MyMoneyMoney a(123, 10000); QVERIFY(a.valueRef().get_num() == 123); QVERIFY(a.valueRef().get_den() == 10000); } void MyMoneyMoneyTest::testAssignment() { MyMoneyMoney *m = new MyMoneyMoney(); *m = *m_1; //qDebug() << "Current value: "<< qPrintable( m->toString()) ; QVERIFY(m->valueRef().get_num() == -1); QVERIFY(m->valueRef().get_den() == 10); //QVERIFY(m->valueRef().get_num() == -10); //QVERIFY(m->valueRef().get_den() == 100); #if 0 *m = 0; QVERIFY(m->valueRef().get_num() == 0); QVERIFY(m->valueRef().get_den() == 100); *m = 777888999; QVERIFY(m->valueRef().get_num() == 777888999); QVERIFY(m->valueRef().get_den() == 100); *m = (int) - 5678; QVERIFY(m->valueRef().get_num() == -5678); QVERIFY(m->valueRef().get_den() == 100); *m = QString("-987"); QVERIFY(m->valueRef().get_num() == -987); QVERIFY(m->valueRef().get_den() == 1); *m = QString("9998887776665554.44"); QVERIFY(m->valueRef().get_num() == 999888777666555444LL); QVERIFY(m->valueRef().get_den() == 100); *m = QString("-99988877766655.444"); QVERIFY(m->valueRef().get_num() == -99988877766655444LL); QVERIFY(m->valueRef().get_den() == 1000); *m = -666555444333222111LL; QVERIFY(m->valueRef().get_num() == -666555444333222111LL); QVERIFY(m->valueRef().get_den() == 100); #endif delete m; } void MyMoneyMoneyTest::testStringConstructor() { MyMoneyMoney *m1 = new MyMoneyMoney("-999666555444"); mpz_class testnum = mpz_class("-999666555444"); //qDebug("Created %s", qPrintable(m1->toString())); QVERIFY(m1->valueRef().get_num() == testnum); QVERIFY(m1->valueRef().get_den() == 1); testnum = mpz_class("444555666999"); MyMoneyMoney *m2 = new MyMoneyMoney("4445556669.99"); QVERIFY(m2->valueRef().get_num() == testnum); QVERIFY(m2->valueRef().get_den() == 100); delete m1; delete m2; //new tests m1 = new MyMoneyMoney("0.01"); QVERIFY(m1->valueRef().get_num() == 1); QVERIFY(m1->valueRef().get_den() == 100); delete m1; m1 = new MyMoneyMoney("0.07"); QVERIFY(m1->valueRef().get_num() == 7); QVERIFY(m1->valueRef().get_den() == 100); delete m1; m1 = new MyMoneyMoney("0.08"); QVERIFY(m1->valueRef().get_num() == 2); QVERIFY(m1->valueRef().get_den() == 25); delete m1; m1 = new MyMoneyMoney("."); //qDebug("Created %s", qPrintable(m1->toString())); QVERIFY(m1->valueRef().get_num() == 0); QVERIFY(m1->valueRef().get_den() == 1); delete m1; m1 = new MyMoneyMoney(""); QVERIFY(m1->valueRef().get_num() == 0); QVERIFY(m1->valueRef().get_den() == 1); delete m1; m1 = new MyMoneyMoney("1,123."); QVERIFY(m1->valueRef().get_num() == (1123)); QVERIFY(m1->valueRef().get_den() == 1); delete m1; m1 = new MyMoneyMoney("123.1"); QVERIFY(m1->valueRef().get_num() == (1231)); QVERIFY(m1->valueRef().get_den() == 10); delete m1; m1 = new MyMoneyMoney("123.456"); //qDebug("Created: %s", m1->valueRef().get_str().c_str()); QVERIFY(m1->valueRef().get_num() == 15432); QVERIFY(m1->valueRef().get_den() == 125); //QVERIFY(m1->valueRef().get_num() == 123456); //QVERIFY(m1->valueRef().get_den() == 1000); delete m1; m1 = new MyMoneyMoney("12345/100"); //qDebug("Created: %s", m1->valueRef().get_str().c_str()); QVERIFY(m1->valueRef().get_num() == 2469); QVERIFY(m1->valueRef().get_den() == 20); // QVERIFY(m1->valueRef().get_num() == (12345)); // QVERIFY(m1->valueRef().get_den() == 100); delete m1; m1 = new MyMoneyMoney("-54321/100"); // qDebug("Created: %s", m1->valueRef().get_str().c_str()); QVERIFY(m1->valueRef().get_num() == (-54321)); QVERIFY(m1->valueRef().get_den() == 100); delete m1; MyMoneyMoney::setDecimalSeparator(','); MyMoneyMoney::setThousandSeparator('.'); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround); m1 = new MyMoneyMoney("x1.234,567 EUR"); QVERIFY(m1->valueRef().get_num() == (1234567)); QVERIFY(m1->valueRef().get_den() == 1000); delete m1; m1 = new MyMoneyMoney("x(1.234,567) EUR"); QVERIFY(m1->valueRef().get_num() == (-1234567)); QVERIFY(m1->valueRef().get_den() == 1000); delete m1; m1 = new MyMoneyMoney("1 5/8"); QVERIFY(m1->valueRef().get_num() == (13)); QVERIFY(m1->valueRef().get_den() == 8); delete m1; m1 = new MyMoneyMoney("09"); QVERIFY(m1->valueRef().get_num() == (9)); QVERIFY(m1->valueRef().get_den() == 1); delete m1; } void MyMoneyMoneyTest::testConvert() { MyMoneyMoney a(123.456); - MyMoneyMoney b = a.convertDenominator(100); + MyMoneyMoney b(a.convertDenominator(100)); QVERIFY(b == MyMoneyMoney(12346 , 100)); a = QString("-123.456"); b = a.convert(100); QVERIFY(b == MyMoneyMoney(-12346 , 100)); a = QString("123.1"); b = a.convert(100); QVERIFY(b == MyMoneyMoney(12310, 100)); a = QString("-73010.28"); b = QString("1.95583"); QVERIFY((a * b).convert(100) == QString("-142795.70")); QVERIFY((a * b).convert(100) == QString("-14279570/100")); // QVERIFY((a * b).convert(100).toString() == QString("-14279570/100")); a = QString("-142795.69"); QVERIFY((a / b).convert(100) == QString("-73010.28")); //QVERIFY((a / b).convert(100).toString() == QString("-7301028/100")); } void MyMoneyMoneyTest::testEquality() { QVERIFY(*m_1 == *m_1); QVERIFY(!(*m_1 == *m_0)); MyMoneyMoney m1(std::int64_t(999666555444), 100); MyMoneyMoney m2(std::int64_t(999666555444), 100); QVERIFY(m1 == m2); MyMoneyMoney m3(std::int64_t(-999666555444), 100); MyMoneyMoney m4(std::int64_t(-999666555444), 100); QVERIFY(m3 == m4); MyMoneyMoney m5(1230, 100); MyMoneyMoney m6(123, 10); MyMoneyMoney m7(246, 20); QVERIFY(m5 == m6); QVERIFY(m5 == m7); QVERIFY(m5 == QString("369/30")); QVERIFY(MyMoneyMoney::autoCalc == MyMoneyMoney::autoCalc); MyMoneyMoney mm1, mm2; mm1 = QLatin1String("-14279570/100"); mm2 = QLatin1String("-1427957/10"); QVERIFY(mm1 == mm2); QVERIFY(mm1 == QLatin1String("-14279570/100")); mm1 = QLatin1String("-7301028/100"); mm2 = QLatin1String("-1825257/25"); QVERIFY(mm1 == mm2); } void MyMoneyMoneyTest::testInequality() { QVERIFY(*m_1 != *m_0); QVERIFY(!(*m_1 != *m_1)); MyMoneyMoney m1(std::int64_t(999666555444), 100); MyMoneyMoney m2(std::int64_t(-999666555444), 100); QVERIFY(m1 != m2); MyMoneyMoney m3(std::int64_t(-999666555444), 100); MyMoneyMoney m4(std::int64_t(999666555444), 100); QVERIFY(m3 != m4); QVERIFY(m4 != QString("999666555444")); QVERIFY(MyMoneyMoney::autoCalc != MyMoneyMoney(1, 100)); QVERIFY(MyMoneyMoney(1, 100) != MyMoneyMoney::autoCalc); } void MyMoneyMoneyTest::testAddition() { QVERIFY(*m_0 + *m_1 == *m_2); MyMoneyMoney m1(100, 100); // QVERIFY((m1 + 50) == MyMoneyMoney(51,1)); // QVERIFY((m1 + 1000000000) == MyMoneyMoney(1000000001,1)); // QVERIFY((m1 + -50) == MyMoneyMoney(-49,1)); QVERIFY((m1 += *m_0) == MyMoneyMoney(112, 100)); // QVERIFY((m1 += -12) == MyMoneyMoney(100)); // m1++; // QVERIFY(m1 == MyMoneyMoney(101)); // QVERIFY((++m1) == MyMoneyMoney(102)); m1 = QString("123.20"); MyMoneyMoney m2(40, 1000); QVERIFY((m1 + m2) == QString("123.24")); m1 += m2; //FIXME check after deciding about normalization QVERIFY(m1.valueRef().get_num() == 3081); QVERIFY(m1.valueRef().get_den() == 25); //QVERIFY(m1.valueRef().get_num() == 123240); //QVERIFY(m1.valueRef().get_den() == 1000); } void MyMoneyMoneyTest::testSubtraction() { QVERIFY(*m_2 - *m_1 == *m_0); MyMoneyMoney m1(100, 100); // QVERIFY((m1-50) == MyMoneyMoney(-49,1)); // QVERIFY((m1-1000000000) == MyMoneyMoney(-999999999,1)); // QVERIFY((m1 - -50) == MyMoneyMoney(51,1)); QVERIFY((m1 -= *m_0) == MyMoneyMoney(88, 100)); // QVERIFY((m1 -= -12) == MyMoneyMoney(100)); // m1--; // QVERIFY(m1 == MyMoneyMoney(99)); // QVERIFY((--m1) == MyMoneyMoney(98)); m1 = QString("123.20"); MyMoneyMoney m2(1, 5); QVERIFY((m1 - m2) == MyMoneyMoney(123, 1)); m1 -= m2; //FIXME check after deciding about normalization QVERIFY(m1.valueRef().get_num() == 123); QVERIFY(m1.valueRef().get_den() == 1); //QVERIFY(m1.valueRef().get_num() == 12300); //QVERIFY(m1.valueRef().get_den() == 100); } void MyMoneyMoneyTest::testMultiplication() { MyMoneyMoney m1(100, 1); QVERIFY((m1 * MyMoneyMoney(50, 1)) == MyMoneyMoney(5000, 1)); QVERIFY((m1 * MyMoneyMoney(10000000, 1)) == MyMoneyMoney(1000000000, 1)); QVERIFY((m1 *(*m_0)) == MyMoneyMoney(1200, 100)); MyMoneyMoney m2(QString("-73010.28")); m1 = QString("1.95583"); QVERIFY((m1 * m2) == QString("-142795.6959324")); MyMoneyMoney m3(100, 1); QVERIFY((m3 * 10) == MyMoneyMoney(1000, 1)); //QVERIFY( (m3 *= (*m_0)) == MyMoneyMoney(1200)); QVERIFY((m3 *= (*m_0)) == MyMoneyMoney(1200, 100)); } void MyMoneyMoneyTest::testDivision() { MyMoneyMoney m1(100, 100); QVERIFY((m1 / MyMoneyMoney(50, 100)) == MyMoneyMoney(2, 1)); MyMoneyMoney m2(QString("-142795.69")); m1 = QString("1.95583"); QVERIFY((m2 / m1).convert(100000000) == QString("-73010.27696681")); MyMoneyMoney m3 = MyMoneyMoney() / MyMoneyMoney(100, 100); QVERIFY(m3.valueRef().get_num() == 0); QVERIFY(m3.valueRef().get_den() != 0); } void MyMoneyMoneyTest::testSetDecimalSeparator() { MyMoneyMoney m1(100000, 100); MyMoneyMoney m2(200000, 100); QVERIFY(m1.formatMoney("", 2) == QString("1,000.00")); QVERIFY(MyMoneyMoney::decimalSeparator() == '.'); MyMoneyMoney::setDecimalSeparator(':'); QVERIFY(m1.formatMoney("", 2) == QString("1,000:00")); QVERIFY(m2.formatMoney("", 2) == QString("2,000:00")); QVERIFY(MyMoneyMoney::decimalSeparator() == ':'); } void MyMoneyMoneyTest::testSetThousandSeparator() { MyMoneyMoney m1(100000, 100); MyMoneyMoney m2(200000, 100); QVERIFY(m1.formatMoney("", 2) == QString("1,000.00")); QVERIFY(MyMoneyMoney::thousandSeparator() == ','); MyMoneyMoney::setThousandSeparator(':'); QVERIFY(m1.formatMoney("", 2) == QString("1:000.00")); QVERIFY(m2.formatMoney("", 2) == QString("2:000.00")); QVERIFY(MyMoneyMoney::thousandSeparator() == ':'); } void MyMoneyMoneyTest::testFormatMoney() { qDebug() << "Value:" << qPrintable(m_0->toString()); qDebug() << "Converted: " << qPrintable(m_0->convert(100).toString()); qDebug() << " Formatted: " << qPrintable(m_0->formatMoney("", 2)); QVERIFY(m_0->formatMoney("", 2) == QString("0.12")); QVERIFY(m_1->formatMoney("", 2) == QString("-0.10")); MyMoneyMoney m1(10099, 100); qDebug() << "Value:" << qPrintable(m1.toString()); qDebug() << "Converted: " << qPrintable(m1.convert(100).toString()); qDebug() << " Formatted: " << qPrintable(m1.formatMoney("", 2)); QVERIFY(m1.formatMoney("", 2) == QString("100.99")); m1 = MyMoneyMoney(100, 1); qDebug() << "Value:" << qPrintable(m1.toString()); qDebug() << "Converted: " << qPrintable(m1.convert(100).toString()); qDebug() << " Formatted: " << qPrintable(m1.formatMoney("", 2)); QVERIFY(m1.formatMoney("", 2) == QString("100.00")); QVERIFY(m1.formatMoney("", -1) == QString("100")); MyMoneyMoney mTemp(100099, 100); m1 = m1 * MyMoneyMoney(10, 1); QVERIFY(m1 == MyMoneyMoney(1000, 1)); QVERIFY(m1.formatMoney("", 2) == QString("1,000.00")); QVERIFY(m1.formatMoney("", -1) == QString("1,000")); QVERIFY(m1.formatMoney("", -1, false) == QString("1000")); QVERIFY(m1.formatMoney("", 3, false) == QString("1000.000")); m1 = MyMoneyMoney(std::numeric_limits::max(), 100); QVERIFY(m1.formatMoney("", 2) == QString("92,233,720,368,547,758.07")); QVERIFY(m1.formatMoney(100) == QString("92,233,720,368,547,758.07")); QVERIFY(m1.formatMoney("", 2, false) == QString("92233720368547758.07")); QVERIFY(m1.formatMoney(100, false) == QString("92233720368547758.07")); m1 = MyMoneyMoney(std::numeric_limits::min(), 100); QVERIFY(m1.formatMoney("", 2) == QString("-92,233,720,368,547,758.08")); QVERIFY(m1.formatMoney(100) == QString("-92,233,720,368,547,758.08")); QVERIFY(m1.formatMoney("", 2, false) == QString("-92233720368547758.08")); QVERIFY(m1.formatMoney(100, false) == QString("-92233720368547758.08")); // make sure we support numbers that need more than 64 bit m1 = MyMoneyMoney(321, 100) * MyMoneyMoney(std::numeric_limits::max(), 100); QVERIFY(m1.formatMoney("", 2) == QString("296,070,242,383,038,303.40")); QVERIFY(m1.formatMoney("", 4) == QString("296,070,242,383,038,303.4047")); QVERIFY(m1.formatMoney("", 6) == QString("296,070,242,383,038,303.404700")); m1 = MyMoneyMoney(1, 5); QVERIFY(m1.formatMoney("", 2) == QString("0.20")); QVERIFY(m1.formatMoney(1000) == QString("0.200")); QVERIFY(m1.formatMoney(100) == QString("0.20")); QVERIFY(m1.formatMoney(10) == QString("0.2")); m1 = MyMoneyMoney(13333, 5000); QVERIFY(m1.formatMoney("", 10) == QString("2.6666000000")); m1 = MyMoneyMoney(-1404, 100); QVERIFY(m1.formatMoney("", -1) == QString("-14.04")); } void MyMoneyMoneyTest::testRelation() { MyMoneyMoney m1(100, 100); MyMoneyMoney m2(50, 100); MyMoneyMoney m3(100, 100); // tests with same denominator QVERIFY(m1 > m2); QVERIFY(m2 < m1); QVERIFY(m1 <= m3); QVERIFY(m3 >= m1); QVERIFY(m1 <= m1); QVERIFY(m3 >= m3); // tests with different denominator m1 = QString("1/8"); m2 = QString("1/7"); QVERIFY(m1 < m2); QVERIFY(m2 > m1); m2 = QString("-1/7"); QVERIFY(m2 < m1); QVERIFY(m1 > m2); QVERIFY(m1 >= m2); QVERIFY(m2 <= m1); m1 = QString("-2/14"); QVERIFY(m1 >= m2); QVERIFY(m1 <= m2); } void MyMoneyMoneyTest::testUnaryMinus() { MyMoneyMoney m1(100, 100); MyMoneyMoney m2; m2 = -m1; QVERIFY(m1 == MyMoneyMoney(100, 100)); QVERIFY(m2 == MyMoneyMoney(-100, 100)); } void MyMoneyMoneyTest::testDoubleConstructor() { for (int i = -123456; i < 123456; ++i) { // int i = -123456; double d = i; MyMoneyMoney r(i, 100); d /= 100; MyMoneyMoney t(d, 100); MyMoneyMoney s(i); QVERIFY(t == r); QVERIFY(i == s.toDouble()); } } void MyMoneyMoneyTest::testAbsoluteFunction() { MyMoneyMoney m1(-100, 100); MyMoneyMoney m2(100, 100); QVERIFY(m2.abs() == MyMoneyMoney(100, 100)); QVERIFY(m1.abs() == MyMoneyMoney(100, 100)); } void MyMoneyMoneyTest::testToString() { MyMoneyMoney m1(-100, 100); MyMoneyMoney m2(1234, 100); MyMoneyMoney m3; //qDebug("Created: %s", m3.valueRef().get_str().c_str()); //QVERIFY(m1.toString() == QString("-100/100")); QVERIFY(m1.toString() == QString("-1/1")); // qDebug("Current value: %s",qPrintable( m2.toString()) ); //QVERIFY(m2.toString() == QString("1234/100")); QVERIFY(m2.toString() == QString("617/50")); QVERIFY(m3.toString() == QString("0/1")); //FIXME check the impact of the canonicalize in the whole code //QVERIFY(m3.toString() == QString("0")); } void MyMoneyMoneyTest::testNegativeSignPos() { MyMoneyMoney m("-123456/100"); MyMoneyMoney::signPosition pos = MyMoneyMoney::negativeMonetarySignPosition(); MyMoneyMoney::setNegativePrefixCurrencySymbol(false); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround); QVERIFY(m.formatMoney("CUR", 2) == "(1,234.56) CUR"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "-1,234.56 CUR"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "1,234.56- CUR"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeMoney); QVERIFY(m.formatMoney("CUR", 2) == "1,234.56 -CUR"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterMoney); QVERIFY(m.formatMoney("CUR", 2) == "1,234.56 CUR-"); MyMoneyMoney::setNegativePrefixCurrencySymbol(true); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround); QVERIFY(m.formatMoney("CUR", 2) == "CUR (1,234.56)"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "CUR -1,234.56"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "CUR 1,234.56-"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeMoney); QVERIFY(m.formatMoney("CUR", 2) == "-CUR 1,234.56"); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::AfterMoney); QVERIFY(m.formatMoney("CUR", 2) == "CUR- 1,234.56"); MyMoneyMoney::setNegativeMonetarySignPosition(pos); } void MyMoneyMoneyTest::testPositiveSignPos() { MyMoneyMoney m("123456/100"); MyMoneyMoney::signPosition pos = MyMoneyMoney::positiveMonetarySignPosition(); MyMoneyMoney::setPositivePrefixCurrencySymbol(false); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::ParensAround); QVERIFY(m.formatMoney("CUR", 2) == "(1,234.56) CUR"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "1,234.56 CUR"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "1,234.56 CUR"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeMoney); QVERIFY(m.formatMoney("CUR", 2) == "1,234.56 CUR"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterMoney); QVERIFY(m.formatMoney("CUR", 2) == "1,234.56 CUR"); MyMoneyMoney::setPositivePrefixCurrencySymbol(true); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::ParensAround); QVERIFY(m.formatMoney("CUR", 2) == "CUR (1,234.56)"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "CUR 1,234.56"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterQuantityMoney); QVERIFY(m.formatMoney("CUR", 2) == "CUR 1,234.56"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::BeforeMoney); QVERIFY(m.formatMoney("CUR", 2) == "CUR 1,234.56"); MyMoneyMoney::setPositiveMonetarySignPosition(MyMoneyMoney::AfterMoney); QVERIFY(m.formatMoney("CUR", 2) == "CUR 1,234.56"); MyMoneyMoney::setPositiveMonetarySignPosition(pos); } void MyMoneyMoneyTest::testNegativeStringConstructor() { MyMoneyMoney *m1; MyMoneyMoney::setDecimalSeparator(','); MyMoneyMoney::setThousandSeparator('.'); MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::ParensAround); m1 = new MyMoneyMoney("x(1.234,567) EUR"); QVERIFY(m1->valueRef().get_num() == (-1234567)); QVERIFY(m1->valueRef().get_den() == 1000); delete m1; MyMoneyMoney::setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney); m1 = new MyMoneyMoney("x1.234,567- EUR"); //qDebug("Created: %s", m1->valueRef().get_str().c_str()); QVERIFY(m1->valueRef().get_num() == (-1234567)); QVERIFY(m1->valueRef().get_den() == 1000); delete m1; m1 = new MyMoneyMoney("x1.234,567 -EUR"); QVERIFY(m1->valueRef().get_num() == (-1234567)); QVERIFY(m1->valueRef().get_den() == 1000); delete m1; m1 = new MyMoneyMoney("-1.234,567 EUR"); QVERIFY(m1->valueRef().get_num() == (-1234567)); QVERIFY(m1->valueRef().get_den() == 1000); delete m1; } void MyMoneyMoneyTest::testReduce() { MyMoneyMoney a(36488100, 1267390000); MyMoneyMoney b(-a); a = a.reduce(); QVERIFY(a.valueRef().get_num() == 364881); QVERIFY(a.valueRef().get_den() == 12673900); b = b.reduce(); QVERIFY(b.valueRef().get_num() == -364881); QVERIFY(b.valueRef().get_den() == 12673900); } void MyMoneyMoneyTest::testZeroDenominator() { try { MyMoneyMoney m((int)1, 0); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } try { MyMoneyMoney m((signed64)1, 0); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } } diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h b/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h index 93f4a2661..f7f1a7937 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h +++ b/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemdelegate.h @@ -1,50 +1,50 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 IBANBICITEMDELEGATE_H #define IBANBICITEMDELEGATE_H #include "payeeidentifier_iban_bic_widgets_export.h" #include #include "payeeidentifier/payeeidentifiertyped.h" #include "payeeidentifier/ibanandbic/ibanbic.h" class PAYEEIDENTIFIER_IBAN_BIC_WIDGETS_EXPORT ibanBicItemDelegate : public QStyledItemDelegate { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kmymoney.payeeIdentifier.ibanbic.delegate" FILE "kmymoney-ibanbic-delegate.json") public: - ibanBicItemDelegate(QObject* parent = nullptr, const QVariantList& args = QVariantList()); + explicit ibanBicItemDelegate(QObject* parent = nullptr, const QVariantList& args = QVariantList()); virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual void setEditorData(QWidget* editor, const QModelIndex& index) const; virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; Q_SIGNALS: void sizeHintChanged(const QModelIndex&) const; private: inline payeeIdentifierTyped ibanBicByIndex(const QModelIndex& index) const; }; #endif // IBANBICITEMDELEGATE_H diff --git a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.h b/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.h index e68aae984..cd78860b0 100644 --- a/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.h +++ b/kmymoney/payeeidentifier/ibanandbic/widgets/ibanbicitemedit.h @@ -1,67 +1,67 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 IBANBICITEMEDIT_H #define IBANBICITEMEDIT_H #include #include namespace Ui { class ibanBicItemEdit; } class ibanBicItemEdit : public QWidget { Q_OBJECT Q_PROPERTY(payeeIdentifier identifier READ identifier WRITE setIdentifier NOTIFY identifierChanged STORED true) Q_PROPERTY(QString iban READ iban WRITE setIban NOTIFY ibanChanged STORED false DESIGNABLE true) Q_PROPERTY(QString bic READ bic WRITE setBic NOTIFY bicChanged STORED false DESIGNABLE true) public: - ibanBicItemEdit(QWidget* parent = 0); + explicit ibanBicItemEdit(QWidget* parent = 0); payeeIdentifier identifier() const; QString iban() const; QString bic() const; public Q_SLOTS: void setIdentifier(const payeeIdentifier&); void setIban(const QString&); void setBic(const QString&); Q_SIGNALS: void commitData(QWidget*); void closeEditor(QWidget* editor); void identifierChanged(payeeIdentifier); void ibanChanged(QString); void bicChanged(QString); private Q_SLOTS: void updateIdentifier(); /** @brief emits commitData(this) and closeEditor(this) */ void editFinished(); private: struct Private; Private* d; }; #endif // IBANBICITEMEDIT_H diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.h b/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.h index 024312bfb..02052c8e3 100644 --- a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.h +++ b/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountdelegate.h @@ -1,48 +1,48 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 NATIONALACCOUNTDELEGATE_H #define NATIONALACCOUNTDELEGATE_H #include #include "../nationalaccount.h" #include "payeeidentifier/payeeidentifiertyped.h" class nationalAccountDelegate : public QStyledItemDelegate { Q_OBJECT public: - nationalAccountDelegate(QObject* parent, const QVariantList& options = QVariantList()); + explicit nationalAccountDelegate(QObject* parent, const QVariantList& options = QVariantList()); virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual void setEditorData(QWidget* editor, const QModelIndex& index) const; virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; signals: void sizeHintChanged(const QModelIndex&) const; private: inline payeeIdentifierTyped identByIndex(const QModelIndex& index) const; }; #endif // NATIONALACCOUNTDELEGATE_H diff --git a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.h b/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.h index 6efa8751e..1ff4bc29f 100644 --- a/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.h +++ b/kmymoney/payeeidentifier/nationalaccount/ui/nationalaccountedit.h @@ -1,63 +1,63 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 NATIONALACCOUNTEDIT_H #define NATIONALACCOUNTEDIT_H #include #include "payeeidentifier/payeeidentifier.h" namespace Ui { class nationalAccountEdit; } class nationalAccountEdit : public QWidget { Q_OBJECT Q_PROPERTY(payeeIdentifier identifier READ identifier WRITE setIdentifier STORED true) Q_PROPERTY(QString accountNumber READ accountNumber WRITE setAccountNumber NOTIFY accountNumberChannged STORED false DESIGNABLE true) Q_PROPERTY(QString institutionCode READ institutionCode WRITE setInstitutionCode NOTIFY institutionCodeChanged STORED false DESIGNABLE true) public: - nationalAccountEdit(QWidget* parent = 0); + explicit nationalAccountEdit(QWidget* parent = 0); payeeIdentifier identifier() const; QString accountNumber() const; QString institutionCode() const; public Q_SLOTS: void setIdentifier(const payeeIdentifier&); void setAccountNumber(const QString&); void setInstitutionCode(const QString&); Q_SIGNALS: void institutionCodeChanged(QString); void accountNumberChannged(QString); void commitData(QWidget*); void closeEditor(QWidget*); private Q_SLOTS: void editFinished(); private: struct Private; Private* d; }; #endif // NATIONALACCOUNTEDIT_H diff --git a/kmymoney/payeeidentifier/unavailableplugin/unavailableplugin.h b/kmymoney/payeeidentifier/unavailableplugin/unavailableplugin.h index 1992182da..a5a468cbf 100644 --- a/kmymoney/payeeidentifier/unavailableplugin/unavailableplugin.h +++ b/kmymoney/payeeidentifier/unavailableplugin/unavailableplugin.h @@ -1,74 +1,74 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 UNAVAILABLEPLUGIN_H #define UNAVAILABLEPLUGIN_H #include "payeeidentifier/payeeidentifierdata.h" #include class payeeIdentifierLoader; namespace payeeIdentifiers { /** * @brief A payeeIdentifier which is used to store the plain xml data * * To avoid data loss if a plugin could not be loaded this payeeIdentifier is used in the xml backend. * It stores the data in plain xml so it can be written back to the file. */ class payeeIdentifierUnavailable : public payeeIdentifierData { public: PAYEEIDENTIFIER_IID(payeeIdentifierUnavailable, "org.kmymoney.payeeIdentifier.payeeIdentifierUnavailable"); payeeIdentifierUnavailable(); virtual void writeXML(QDomDocument& document, QDomElement& parent) const; virtual payeeIdentifierUnavailable* createFromXml(const QDomElement& element) const; virtual bool isValid() const; virtual bool operator==(const payeeIdentifierData& other) const; bool operator==(const payeeIdentifierUnavailable& other) const; friend class payeeIdentifierLoader; /** @todo make private */ - payeeIdentifierUnavailable(QDomElement data); + explicit payeeIdentifierUnavailable(QDomElement data); /** * @name SqlMethods * @{ * For SQL databases this plugin is not needed nor used. So these functions * do not have a real implementation. */ virtual QString storagePluginIid() const; virtual bool sqlSave(QSqlDatabase databaseConnection, const QString& onlineJobId) const; virtual bool sqlModify(QSqlDatabase databaseConnection, const QString& onlineJobId) const; virtual bool sqlRemove(QSqlDatabase databaseConnection, const QString& onlineJobId) const; virtual payeeIdentifierData* createFromSqlDatabase(QSqlDatabase db, const QString& identId) const; /** @} */ protected: virtual payeeIdentifierUnavailable* clone() const; private: QDomElement m_data; }; } // namespace payeeidentifiers #endif // UNAVAILABLEPLUGIN_H diff --git a/kmymoney/pluginloader.h b/kmymoney/pluginloader.h index 0ff3f1bc3..79b864be6 100644 --- a/kmymoney/pluginloader.h +++ b/kmymoney/pluginloader.h @@ -1,95 +1,95 @@ /*************************************************************************** pluginloader.h ------------------- begin : Thu Feb 12 2009 copyright : (C) 2009 Cristian Onet email : onet.cristian@gmail.com ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef PLUGINLOADER_H #define PLUGINLOADER_H #include #include #include class KPluginSelector; class KConfigGroup; namespace KMyMoneyPlugin { class Plugin; /** * @brief User Interface for plugins * * For historic reasons it is still called plugin loader even though it does not load any plugin anymore. */ class PluginLoader : public QObject { Q_OBJECT public: - PluginLoader(QObject* parent); + explicit PluginLoader(QObject* parent); /** * Needed to delete the unique_ptr which is of incomplete type in the header file */ virtual ~PluginLoader(); static PluginLoader* instance(); KPluginSelector* pluginSelectorWidget(); static inline bool isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup); public slots: /** @brief Adds the given plugins to the plugin selection ui */ void addPluginInfo(const QVector& metaData); /** @brief Find all available plugins */ void detectPlugins(); signals: /** Load the shared module and emits plug() */ void pluginEnabled(const KPluginMetaData& metaData); void pluginDisabled(const KPluginMetaData& metaData); void configChanged(Plugin*); // configuration of the plugin has changed not the enabled/disabled state private slots: void changed(); private: KPluginSelector* m_pluginSelector; /** * @{ * Translated strings * * They are created on creation time because they are used as identifiers. */ QString m_categoryKMyMoneyPlugin; QString m_categoryOnlineTask; QString m_categoryPayeeIdentifier; /** @} */ /** * @brief All plugins which are listed in the UI * * The set contains the plugin file name. * Maybe it can/should be replaced by something different. */ QSet m_displayedPlugins; QString categoryByPluginType(const KPluginMetaData& mataData); }; } #endif /* PLUGINLOADER_H */ diff --git a/kmymoney/plugins/csvimport/kcm_csvimport.h b/kmymoney/plugins/csvimport/kcm_csvimport.h index aebe18f42..fbe5c3d81 100644 --- a/kmymoney/plugins/csvimport/kcm_csvimport.h +++ b/kmymoney/plugins/csvimport/kcm_csvimport.h @@ -1,46 +1,46 @@ /*************************************************************************** * Copyright 2016 Łukasz Wojniłowicz lukasz.wojnilowicz@gmail.com * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 KCM_CSVIMPORT_H #define KCM_CSVIMPORT_H #include #include #include "ui_pluginsettingsdecl.h" class PluginSettingsWidget : public QWidget, public Ui::PluginSettingsDecl { Q_OBJECT public: - PluginSettingsWidget(QWidget* parent = 0); + explicit PluginSettingsWidget(QWidget* parent = 0); public slots: private: }; class KCMcsvimport : public KCModule { public: - KCMcsvimport(QWidget* parent, const QVariantList& args); + explicit KCMcsvimport(QWidget* parent, const QVariantList& args); ~KCMcsvimport(); }; #endif // KCM_CSVIMPORT_H diff --git a/kmymoney/plugins/kbanking/gwenkdegui.h b/kmymoney/plugins/kbanking/gwenkdegui.h index 493dec470..90a650567 100644 --- a/kmymoney/plugins/kbanking/gwenkdegui.h +++ b/kmymoney/plugins/kbanking/gwenkdegui.h @@ -1,91 +1,91 @@ /* * A gwenhywfar gui for aqbanking using KDE widgets * Copyright 2014 - 2016 Christian David * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 GWENKDEGUI_H #define GWENKDEGUI_H #include #include "gwen-gui-qt5/qt5_gui.hpp" /** * @brief Gwenhywfar Gui by KDE * * * @author Christian David */ class gwenKdeGui : public QT5_Gui { public: gwenKdeGui(); ~gwenKdeGui(); virtual int getPassword(uint32_t flags, const char *token, const char *title, const char *text, char *buffer, int minLen, int maxLen, uint32_t guiid); }; /** * @brief Helper class which is receiver for several signals */ class gwenKdeGuiTanResult : public QObject { Q_OBJECT public: - gwenKdeGuiTanResult(QObject* parent = nullptr) + explicit gwenKdeGuiTanResult(QObject* parent = nullptr) : QObject(parent), m_tan(QString()), m_aborted(false) {} virtual ~gwenKdeGuiTanResult() {} QString tan() { return m_tan; } bool aborted() { return m_aborted; } public slots: void abort() { m_aborted = true; } void acceptTan(QString tan) { m_tan = tan; m_aborted = false; } private: QString m_tan; bool m_aborted; }; #endif // GWENKDEGUI_H diff --git a/kmymoney/plugins/kbanking/widgets/chiptandialog.h b/kmymoney/plugins/kbanking/widgets/chiptandialog.h index 941982148..40b84545b 100644 --- a/kmymoney/plugins/kbanking/widgets/chiptandialog.h +++ b/kmymoney/plugins/kbanking/widgets/chiptandialog.h @@ -1,77 +1,77 @@ /* * A tan input dialog for optical chipTan used in online banking * Copyright 2014 Christian David * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 CHIPTANDIALOG_H #define CHIPTANDIALOG_H #include #include namespace Ui { class chipTanDialog; } class chipTanDialog : public QDialog { Q_OBJECT Q_PROPERTY(QString infoText READ infoText() WRITE setInfoText) Q_PROPERTY(QString hhdCode READ hhdCode() WRITE setHhdCode) Q_PROPERTY(int flickerFieldWidth READ flickerFieldWidth WRITE setFlickerFieldWidth) public: - chipTanDialog(QWidget* parent = 0); + explicit chipTanDialog(QWidget* parent = 0); ~chipTanDialog(); enum Result { Accepted = 0, Rejected, InternalError }; QString infoText(); QString hhdCode(); QString tan(); int flickerFieldWidth(); public slots: void accept(); void reject(); void setInfoText(const QString&); void setHhdCode(const QString&); void setTanLimits(const int& minLength, const int& maxLength); void setFlickerFieldWidth(const int& width); void setFlickerFieldClockSetting(const int& width); private slots: void tanInputChanged(const QString&); void flickerFieldWidthChanged(const int& width); void flickerFieldClockSettingChanged(const int& takt); private: std::unique_ptr ui; QString m_tan; bool m_accepted; void setRootObjectProperty(const char* property, const QVariant& value); }; #endif // CHIPTANDIALOG_H diff --git a/kmymoney/plugins/kbanking/widgets/kbaccountlist.h b/kmymoney/plugins/kbanking/widgets/kbaccountlist.h index aeb873d9b..bb13f124a 100644 --- a/kmymoney/plugins/kbanking/widgets/kbaccountlist.h +++ b/kmymoney/plugins/kbanking/widgets/kbaccountlist.h @@ -1,74 +1,74 @@ /*************************************************************************** begin : Mon Mar 01 2004 copyright : (C) 2004 by Martin Preuss email : martin@libchipcard.de *************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KBACCOUNTLIST_H #define KBACCOUNTLIST_H #include #include #include class KBAccountListView; class KBAccountListViewItem; class KBAccountListViewItem: public QTreeWidgetItem { private: AB_ACCOUNT *_account; void _populate(); bool operator< (const QTreeWidgetItem & other) const; //!< correctly sort text columns, which contain numbers public: KBAccountListViewItem(KBAccountListView *parent, AB_ACCOUNT *acc); KBAccountListViewItem(KBAccountListView *parent, QTreeWidgetItem *after, AB_ACCOUNT *acc); KBAccountListViewItem(const KBAccountListViewItem &item); virtual ~KBAccountListViewItem(); AB_ACCOUNT *getAccount(); }; class KBAccountListView: public QTreeWidget { private: public: - KBAccountListView(QWidget *parent = 0); + explicit KBAccountListView(QWidget *parent = 0); virtual ~KBAccountListView(); void addAccount(AB_ACCOUNT *acc); void addAccounts(const std::list &accs); AB_ACCOUNT *getCurrentAccount(); std::list getSelectedAccounts(); std::list getSortedAccounts(); }; #endif /* QBANKING_ACCOUNTLIST_H */ diff --git a/kmymoney/plugins/kbanking/widgets/kbjoblist.h b/kmymoney/plugins/kbanking/widgets/kbjoblist.h index ef35dd733..e8df6b25f 100644 --- a/kmymoney/plugins/kbanking/widgets/kbjoblist.h +++ b/kmymoney/plugins/kbanking/widgets/kbjoblist.h @@ -1,71 +1,71 @@ /*************************************************************************** * Copyright 2009 Cristian Onet onet.cristian@gmail.com * * Copyright 2004 Martin Preuss aquamaniac@users.sourceforge.net * * Copyright 2010 Thomas Baumgart ipwizard@users.sourceforge.net * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 AQHBCI_KBJOBLIST_H #define AQHBCI_KBJOBLIST_H #include #include #include class KBJobListView; class KBJobListViewItem; class KBJobListViewItem: public QTreeWidgetItem { private: AB_JOB *_job; void _populate(); public: KBJobListViewItem(KBJobListView *parent, AB_JOB *j); KBJobListViewItem(const KBJobListViewItem &item); virtual ~KBJobListViewItem(); AB_JOB *getJob(); }; class KBJobListView: public QTreeWidget { private: public: - KBJobListView(QWidget *parent = 0); + explicit KBJobListView(QWidget *parent = 0); virtual ~KBJobListView(); void addJob(AB_JOB *j); void addJobs(const std::list &js); AB_JOB *getCurrentJob(); std::list getSelectedJobs(); }; #endif /* AQHBCI_KBJOBLIST_H */ diff --git a/kmymoney/plugins/kmymoneyplugin.h b/kmymoney/plugins/kmymoneyplugin.h index a6a690190..7d93a1d6e 100644 --- a/kmymoney/plugins/kmymoneyplugin.h +++ b/kmymoney/plugins/kmymoneyplugin.h @@ -1,281 +1,281 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2005 Thomas Baumgart * Copyright (C) 2015 Christian Dávid * * 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 KMYMONEYPLUGIN_H #define KMYMONEYPLUGIN_H #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include class KToggleAction; // ---------------------------------------------------------------------------- // Project Includes #include "mymoneykeyvaluecontainer.h" class MyMoneyAccount; namespace KMyMoneyPlugin { class ImportInterface; } namespace KMyMoneyPlugin { class StatementInterface; } namespace KMyMoneyPlugin { class ViewInterface; } /** * @defgroup KMyMoneyPlugin * * KMyMoney knows several types of plugins. The most common and generic one is KMyMoneyPlugin::Plugin. * * Another group of plugins are just loaded on demand and offer special functions with a tight integration into KMyMoney. Whenever possible you should use this kind of plugins. * At the moment this are the onlineTask and payeeIdentifierData. * * @{ */ namespace KMyMoneyPlugin { /** * This class describes the interface between KMyMoney and it's plugins. * * The plugins are based on Qt 5's plugin system. So you must compile json information into the plugin. * KMyMoney looks into the folder "${PLUGIN_INSTALL_DIR}/kmymoney/" and loads all plugins found there (if the user did not deactivate the plugin). * * The json header of the plugin must comply with the requirements of KCoreAddon's KPluginMetaData class. * To load the plugin at start up the service type "KMyMoney/Plugin" must be set. * * @warning The plugin system for KMyMoney 5 is still in development. Especially the loading of the on-demand plugins (mainly undocumented :( ) will change. * * A basic json header is shown below. * @code{.json} { "KPlugin": { "Authors": [ { "Name": "Author's Names, Second Author", "Email": "E-Mail 1, E-Mail 2" } ], "Description": "Short description for plugin list (translateable)", "EnabledByDefault": true, "Icon": "icon to be shown in plugin list", "Id": "a unique identifier", "License": "see KPluginMetaData for list of predefined licenses (translateable)", "Name": "Name of the plugin (translateable)", "ServiceTypes": [ "KMyMoney/Plugin" ], "Version": "@PROJECT_VERSION@@PROJECT_VERSION_SUFFIX@", } } * @endcode * * This example assumes you are using * @code{.cmake} configure_file(${CMAKE_CURRENT_SOURCE_DIR}/... ${CMAKE_CURRENT_BINARY_DIR}/... @ONLY) @endcode * to replace the version variables using cmake. * * @see http://doc.qt.io/qt-5/plugins-howto.html * @see https://api.kde.org/frameworks/kcoreaddons/html/classKPluginMetaData.html * */ class KMM_PLUGIN_EXPORT Plugin : public QObject, public KXMLGUIClient { Q_OBJECT public: - Plugin(QObject* parent = nullptr, const char* name = ""); + explicit Plugin(QObject* parent = nullptr, const char* name = ""); virtual ~Plugin(); public slots: /** * @brief Called during plug in process */ virtual void plug(); /** * @brief Called before unloading */ virtual void unplug(); /** * @brief Called if the configuration of the plugin was changed * @todo Implement */ virtual void configurationChanged() ; protected: /** See KMyMoneyApp::toggleAction() for a description */ KToggleAction* toggleAction(const QString& name) const; // define interface classes here. The interface classes provide a mechanism // for the plugin to interact with KMyMoney // they are defined in the following form for an interface // named Xxx: // // XxxInterface* xxxInterface(); ViewInterface* viewInterface() const; StatementInterface* statementInterface() const; ImportInterface* importInterface() const; }; /** * This class describes the interface between the KMyMoney * application and it's ONLINE-BANKING plugins. All online banking plugins * must provide this interface. * * A good tutorial on how to design and develop a plugin * structure for a KDE application (e.g. KMyMoney) can be found at * http://developer.kde.org/documentation/tutorials/developing-a-plugin-structure/index.html * */ class KMM_PLUGIN_EXPORT OnlinePlugin { public: OnlinePlugin() {} virtual ~OnlinePlugin() {} virtual void protocols(QStringList& protocolList) const = 0; /** * This method returns a pointer to a widget representing an additional * tab that will be added to the KNewAccountDlg. The string referenced * with @a tabName will be filled with the text that should be placed * on the tab. It should return 0 if no additional tab is needed. * * Information about the account can be taken out of @a account. * * Once the pointer to the widget is returned to KMyMoney, it takes care * of destruction of all included widgets when the dialog is closed. The plugin * can access the widgets created after the call to storeConfigParameters() * happened. */ virtual QWidget* accountConfigTab(const MyMoneyAccount& account, QString& tabName) = 0; /** * This method is called by the framework whenever it is time to store * the configuration data maintained by the plugin. The plugin should use * the widgets created in accountConfigTab() to extract the current values. * * @param current The @a current container contains the current settings */ virtual MyMoneyKeyValueContainer onlineBankingSettings(const MyMoneyKeyValueContainer& current) = 0; /** * This method is called by the framework when the user wants to map * a KMyMoney account onto an online account. The KMyMoney account is identified * by @a acc and the online provider should store its data in @a onlineBankingSettings * upon success. * * @retval true if account is mapped * @retval false if account is not mapped */ virtual bool mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& onlineBankingSettings) = 0; /** * This method is called by the framework when the user wants to update * a KMyMoney account with data from an online account. The KMyMoney account is identified * by @a acc. The online provider should read its data from acc.onlineBankingSettings(). * @a true is returned upon success. The plugin might consider to stack the requests * in case @a moreAccounts is @p true. @a moreAccounts defaults to @p false. * * @retval true if account is updated * @retval false if account is not updated */ virtual bool updateAccount(const MyMoneyAccount& acc, bool moreAccounts = false) = 0; }; /** * This class describes the interface between the KMyMoney * application and it's IMPORTER plugins. All importer plugins * must provide this interface. * * A good tutorial on how to design and develop a plugin * structure for a KDE application (e.g. KMyMoney) can be found at * http://developer.kde.org/documentation/tutorials/developing-a-plugin-structure/index.html * */ class KMM_PLUGIN_EXPORT ImporterPlugin { public: ImporterPlugin() {} virtual ~ImporterPlugin() {} /** * This method returns the english-language name of the format * this plugin imports, e.g. "OFX" * * @return QString Name of the format */ virtual QString formatName() const = 0; /** * This method returns the filename filter suitable for passing to * KFileDialog::setFilter(), e.g. "*.ofx *.qfx" which describes how * files of this format are likely to be named in the file system * * @return QString Filename filter string */ virtual QString formatFilenameFilter() const = 0; /** * This method returns whether this plugin is able to import * a particular file. * * @param filename Fully-qualified pathname to a file * * @return bool Whether the indicated file is importable by this plugin */ virtual bool isMyFormat(const QString& filename) const = 0; /** * Import a file * * @param filename File to import * * @return bool Whether the import was successful. */ virtual bool import(const QString& filename) = 0; /** * Returns the error result of the last import * * @return QString English-language name of the error encountered in the * last import, or QString() if it was successful. * */ virtual QString lastError() const = 0; }; } // end of namespace Q_DECLARE_INTERFACE(KMyMoneyPlugin::OnlinePlugin, "org.kmymoney.plugin.onlineplugin") Q_DECLARE_INTERFACE(KMyMoneyPlugin::ImporterPlugin, "org.kmymoney.plugin.importerplugin") /** @} */ #endif diff --git a/kmymoney/plugins/ofximport/dialogs/kofxdirectconnectdlg.h b/kmymoney/plugins/ofximport/dialogs/kofxdirectconnectdlg.h index 0e9bdb6a0..eac8c9528 100644 --- a/kmymoney/plugins/ofximport/dialogs/kofxdirectconnectdlg.h +++ b/kmymoney/plugins/ofximport/dialogs/kofxdirectconnectdlg.h @@ -1,96 +1,96 @@ /*************************************************************************** kofxdirectconnectdlg.h ------------------- begin : Sat Nov 13 2004 copyright : (C) 2002 by Ace Jones email : acejones@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KOFXDIRECTCONNECTDLG_H #define KOFXDIRECTCONNECTDLG_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes class QTemporaryFile; class KJob; namespace KIO { class Job; class TransferJob; } // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyofxconnector.h" #include "ui_kofxdirectconnectdlgdecl.h" /** @author ace jones */ class KOfxDirectConnectDlgDecl : public QDialog, public Ui::KOfxDirectConnectDlgDecl { public: - KOfxDirectConnectDlgDecl(QWidget *parent) : QDialog(parent) { + explicit KOfxDirectConnectDlgDecl(QWidget *parent) : QDialog(parent) { setupUi(this); } }; class KOfxDirectConnectDlg : public KOfxDirectConnectDlgDecl { Q_OBJECT public: explicit KOfxDirectConnectDlg(const MyMoneyAccount&, QWidget *parent = 0); ~KOfxDirectConnectDlg(); /** * Initializes the download of OFX statement data. * * @returns true if download was initialized * @returns false if download was not started */ bool init(); signals: /** * This signal is emitted when the statement is downloaded * and stored in file @a fname. */ void statementReady(const QString& fname); protected slots: void slotOfxFinished(KJob*); void slotOfxData(KIO::Job*, const QByteArray&); virtual void reject(); protected: void setStatus(const QString& _status); void setDetails(const QString& _details); private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; QTemporaryFile* m_tmpfile; MyMoneyOfxConnector m_connector; KIO::TransferJob* m_job; }; #endif // KOFXDIRECTCONNECTDLG_H diff --git a/kmymoney/plugins/ofximport/dialogs/konlinebankingsetupwizard.h b/kmymoney/plugins/ofximport/dialogs/konlinebankingsetupwizard.h index a2d2c3019..9e1a25809 100644 --- a/kmymoney/plugins/ofximport/dialogs/konlinebankingsetupwizard.h +++ b/kmymoney/plugins/ofximport/dialogs/konlinebankingsetupwizard.h @@ -1,110 +1,110 @@ /*************************************************************************** konlinebankingsetupwizard.h ------------------- begin : Sat Jan 7 2006 copyright : (C) 2006 by Ace Jones email : acejones@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KONLINEBANKINGSETUPWIZARD_H #define KONLINEBANKINGSETUPWIZARD_H // ---------------------------------------------------------------------------- // Library Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_konlinebankingsetupwizard.h" #include "mymoneykeyvaluecontainer.h" class OfxAppVersion; class OfxHeaderVersion; /** * @author Ace Jones */ /** * This class implementes a wizard for setting up an existing account * with online banking. * * The user is asked to choose his bank from the supported bank, and * his account. * * Currently works only with OFX Direct Connect, but I imagined that * other protocols could be included here. To accommodate this, we'd * add another page at the start of the wizard to ask which protocol * they wanted. * */ class KOnlineBankingSetupWizard : public QWizard, public Ui::KOnlineBankingSetupWizard { Q_OBJECT public: class ListViewItem: public MyMoneyKeyValueContainer, public QTreeWidgetItem { public: ListViewItem(QTreeWidget* parent, const MyMoneyKeyValueContainer& kvps); // virtual void x(); }; - KOnlineBankingSetupWizard(QWidget *parent = 0); + explicit KOnlineBankingSetupWizard(QWidget *parent = 0); ~KOnlineBankingSetupWizard(); bool chosenSettings(MyMoneyKeyValueContainer& settings); bool isInit() const { return m_fInit; } protected slots: void checkNextButton(); void newPage(int id); void walletOpened(bool ok); void applicationSelectionChanged(); protected: bool finishAccountPage(); bool finishLoginPage(); bool finishFiPage(); bool post(const char* request, const char* url, const char* filename); static int ofxAccountCallback(struct OfxAccountData data, void * pv); static int ofxStatusCallback(struct OfxStatusData data, void * pv); private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; QList m_bankInfo; QList::const_iterator m_it_info; bool m_fDone; bool m_fInit; OfxAppVersion* m_appId; OfxHeaderVersion* m_headerVersion; }; #endif diff --git a/kmymoney/plugins/ofximport/dialogs/konlinebankingstatus.h b/kmymoney/plugins/ofximport/dialogs/konlinebankingstatus.h index a2b62677d..66edaca65 100644 --- a/kmymoney/plugins/ofximport/dialogs/konlinebankingstatus.h +++ b/kmymoney/plugins/ofximport/dialogs/konlinebankingstatus.h @@ -1,68 +1,68 @@ /*************************************************************************** konlinebankingstatus.h ------------------- begin : Wed Apr 16 2008 copyright : (C) 2008 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KONLINEBANKINGSTATUS_H #define KONLINEBANKINGSTATUS_H // ---------------------------------------------------------------------------- // Library Includes // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_konlinebankingstatusdecl.h" class MyMoneyAccount; class OfxAppVersion; class OfxHeaderVersion; /** * @author Thomas Baumgart */ class KOnlineBankingStatusDecl : public QWidget, public Ui::KOnlineBankingStatusDecl { public: - KOnlineBankingStatusDecl(QWidget *parent) : QWidget(parent) { + explicit KOnlineBankingStatusDecl(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class KOnlineBankingStatus : public KOnlineBankingStatusDecl { Q_OBJECT public: explicit KOnlineBankingStatus(const MyMoneyAccount& acc, QWidget *parent = 0); ~KOnlineBankingStatus(); const QString appId() const; QString headerVersion() const; protected Q_SLOTS: void applicationSelectionChanged(); private: OfxAppVersion* m_appId; OfxHeaderVersion* m_headerVersion; }; #endif diff --git a/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.h b/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.h index c2c93e44a..533f1b76b 100644 --- a/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.h +++ b/kmymoney/plugins/ofximport/dialogs/mymoneyofxconnector.h @@ -1,137 +1,137 @@ /*************************************************************************** mymoneyofxconnector.cpp ------------------- begin : Sat Nov 13 2004 copyright : (C) 2002 by Ace Jones email : acejones@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef MYMONEYOFXCONNECTOR_H #define MYMONEYOFXCONNECTOR_H // ---------------------------------------------------------------------------- // Library Includes #include // if OFX has a major version number defined, we'll take it // if not, we assume 0.9.4. since that's the minimum version #ifdef LIBOFX_MAJOR_VERSION #define LIBOFX_VERSION KDE_MAKE_VERSION(LIBOFX_MAJOR_VERSION, LIBOFX_MINOR_VERSION, LIBOFX_MICRO_VERSION) #else #define LIBOFX_VERSION KDE_MAKE_VERSION(0,9,4) #endif #define LIBOFX_IS_VERSION(a,b,c) (LIBOFX_VERSION >= KDE_MAKE_VERSION(a,b,c)) // ---------------------------------------------------------------------------- // QT Includes #include class QDate; // ---------------------------------------------------------------------------- // KDE Includes class KComboBox; class KLineEdit; // ---------------------------------------------------------------------------- // Project Includes #include "mymoneykeyvaluecontainer.h" class MyMoneyAccount; #define OFX_PASSWORD_KEY(url, id) QString("KMyMoney-OFX-%1-%2").arg(url, id) /** * @author Thomas Baumgart */ class OfxAppVersion { public: OfxAppVersion(KComboBox* combo, KLineEdit* versionEdit, const QString& appId); /** * This method returns the currently selected application id * as a colon separated value consisting of the application * and version (eg. "QWIN:1700"). If current value is the * default, an empty string is returned. */ const QString appId() const; /** * This method returns @c true in case the current selected application * version is valid (contains app and version) or @c false otherwise. */ bool isValid() const; private: QMap m_appMap; KComboBox* m_combo; KLineEdit* m_versionEdit; }; /** * @author Thomas Baumgart */ class OfxHeaderVersion { public: OfxHeaderVersion(KComboBox* combo, const QString& headerVersion); QString headerVersion() const; private: KComboBox* m_combo; }; /** @author ace jones */ class MyMoneyOfxConnector { public: - MyMoneyOfxConnector(const MyMoneyAccount& _account); + explicit MyMoneyOfxConnector(const MyMoneyAccount& _account); QString url() const; /** * Constructs the request for a statement. The first date * for which transactions will be requested is determined * by statementStartDate() */ const QByteArray statementRequest() const; const QByteArray statementResponse(const QDate& _dtstart) const; private: void initRequest(OfxFiLogin* fi) const; QDate statementStartDate() const; QString iban() const; QString fiorg() const; QString fiid() const; QString clientUid() const; QString username() const; QString password() const; QString accountnum() const; OfxAccountData::AccountType accounttype() const; private: const MyMoneyAccount& m_account; MyMoneyKeyValueContainer m_fiSettings; }; // open a synchronous wallet in a safe way (the function is here because the wallet is only used in the OFX plugin) namespace KWallet { class Wallet; } KWallet::Wallet *openSynchronousWallet(); #endif // OFXCONNECTOR_H diff --git a/kmymoney/plugins/ofximport/nodeparser.h b/kmymoney/plugins/ofximport/nodeparser.h index be7f111c1..80f504c39 100644 --- a/kmymoney/plugins/ofximport/nodeparser.h +++ b/kmymoney/plugins/ofximport/nodeparser.h @@ -1,41 +1,41 @@ /*************************************************************************** nodeparse.h ------------------- copyright : (C) 2008 by Thomas Baumgart email : ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef NODEPARSER_H #define NODEPARSER_H #include #include #include class NodeParser: public xmlpp::Node::NodeList { public: NodeParser(void) {} - NodeParser(const xmlpp::Node::NodeList&); - NodeParser(const xmlpp::Node*); - NodeParser(const xmlpp::DomParser&); + explicit NodeParser(const xmlpp::Node::NodeList&); + explicit NodeParser(const xmlpp::Node*); + explicit NodeParser(const xmlpp::DomParser&); NodeParser Path(const std::string& path) const; NodeParser Select(const std::string& key, const std::string& value) const; std::vector Text(void) const; protected: static NodeParser Path(const xmlpp::Node* node, const std::string& path); }; #endif // NODEPARSER_H diff --git a/kmymoney/plugins/onlinetasks/sepa/sepaonlinetasksloader.h b/kmymoney/plugins/onlinetasks/sepa/sepaonlinetasksloader.h index c6fac6ef6..74ac36f1d 100644 --- a/kmymoney/plugins/onlinetasks/sepa/sepaonlinetasksloader.h +++ b/kmymoney/plugins/onlinetasks/sepa/sepaonlinetasksloader.h @@ -1,34 +1,35 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2013 Christian Dávid * * 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 SEPAONLINETASKSLOADER_H #define SEPAONLINETASKSLOADER_H #include class sepaOnlineTasksLoader : public QObject, public KMyMoneyPlugin::onlineTaskFactory { Q_OBJECT Q_INTERFACES(KMyMoneyPlugin::onlineTaskFactory) + public: - sepaOnlineTasksLoader(QObject* parent = nullptr, const QVariantList& options = QVariantList{}); + explicit sepaOnlineTasksLoader(QObject* parent = nullptr, const QVariantList& options = QVariantList{}); virtual onlineTask* createOnlineTask(const QString& taskId) const; }; #endif // SEPAONLINETASKSLOADER_H diff --git a/kmymoney/plugins/onlinetasks/sepa/sepastorageplugin.h b/kmymoney/plugins/onlinetasks/sepa/sepastorageplugin.h index d44b68c24..ca4dc0394 100644 --- a/kmymoney/plugins/onlinetasks/sepa/sepastorageplugin.h +++ b/kmymoney/plugins/onlinetasks/sepa/sepastorageplugin.h @@ -1,39 +1,39 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 SEPASTORAGEPLUGIN_H #define SEPASTORAGEPLUGIN_H #include "kmymoneystorageplugin.h" #include class sepaStoragePlugin : public KMyMoneyPlugin::storagePlugin { Q_OBJECT Q_INTERFACES(KMyMoneyPlugin::storagePlugin) public: - sepaStoragePlugin(QObject* parent = 0, const QVariantList& options = QVariantList()); + explicit sepaStoragePlugin(QObject* parent = 0, const QVariantList& options = QVariantList()); virtual bool removePluginData(QSqlDatabase connection); virtual bool setupDatabase(QSqlDatabase connection); static const QString iid; }; #endif // SEPASTORAGEPLUGIN_H diff --git a/kmymoney/plugins/printcheck/kcm_printcheck.h b/kmymoney/plugins/printcheck/kcm_printcheck.h index 434c96e11..0271111ee 100644 --- a/kmymoney/plugins/printcheck/kcm_printcheck.h +++ b/kmymoney/plugins/printcheck/kcm_printcheck.h @@ -1,61 +1,62 @@ /*************************************************************************** * Copyright 2009 Cristian Onet onet.cristian@gmail.com * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 KCM_PRINTCHECK_H #define KCM_PRINTCHECK_H #include #include #include #include "ui_pluginsettingsdecl.h" #ifdef ENABLE_WEBENGINE class QWebEngineView; #else class KWebView; #endif class PluginSettingsWidget : public QWidget, public Ui::PluginSettingsDecl { Q_OBJECT + public: - PluginSettingsWidget(QWidget* parent = 0); + explicit PluginSettingsWidget(QWidget* parent = 0); public slots: void urlSelected(const QUrl &url); void returnPressed(const QString& url); private: #ifdef ENABLE_WEBENGINE QWebEngineView *m_checkTemplatePreviewHTMLPart; #else KWebView *m_checkTemplatePreviewHTMLPart; #endif }; class KCMPrintCheck : public KCModule { public: KCMPrintCheck(QWidget* parent, const QVariantList& args); ~KCMPrintCheck(); }; #endif // KCM_PRINTCHECK_H diff --git a/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.h b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.h index 11e2a41e8..9b15678ec 100644 --- a/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.h +++ b/kmymoney/plugins/qif/config/mymoneyqifprofileeditor.h @@ -1,93 +1,93 @@ /*************************************************************************** kqifprofileeditor.h - description ------------------- begin : Tue Dec 24 2002 copyright : (C) 2002 by Thomas Baumgart email : thb@net-bembel.de ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef MYMONEYQIFPROFILEEDITOR_H #define MYMONEYQIFPROFILEEDITOR_H // ---------------------------------------------------------------------------- // QT Includes #include #include class QTreeWidgetItem; // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_mymoneyqifprofileeditor.h" #include "mymoneyqifprofile.h" /** * @author Thomas Baumgart */ class MyMoneyQifProfileNameValidator : public QValidator { Q_OBJECT public: - MyMoneyQifProfileNameValidator(QObject* o); + explicit MyMoneyQifProfileNameValidator(QObject* o); virtual ~MyMoneyQifProfileNameValidator(); QValidator::State validate(QString&, int&) const; }; class MyMoneyQifProfileEditor : public QWidget, public Ui::MyMoneyQifProfileEditor { Q_OBJECT public: explicit MyMoneyQifProfileEditor(const bool edit = false, QWidget *parent = 0); virtual ~MyMoneyQifProfileEditor(); /** * This method returns the currently selected profile in the list box. */ const QString selectedProfile() const; protected slots: void slotLoadProfileFromConfig(const QString& name); void slotReset(); void slotRename(); void slotDelete(); void slotNew(); void slotAmountTypeSelected(); void slotDecimalChanged(const QString& val); void slotThousandsChanged(const QString& val); void slotHelp(); private: void loadProfileListFromConfig(); void loadWidgets(); void showProfile(); void addProfile(const QString& name); void deleteProfile(const QString& name); const QString enterName(bool& ok); private: bool m_inEdit; MyMoneyQifProfile m_profile; bool m_isDirty; bool m_isAccepted; QTreeWidgetItem* m_selectedAmountType; }; #endif diff --git a/kmymoney/plugins/qif/export/kexportdlg.h b/kmymoney/plugins/qif/export/kexportdlg.h index 38dc64a66..aa5d0a8cc 100644 --- a/kmymoney/plugins/qif/export/kexportdlg.h +++ b/kmymoney/plugins/qif/export/kexportdlg.h @@ -1,196 +1,196 @@ /*************************************************************************** kexportdlg.h - description ------------------- begin : Tue May 22 2001 copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KEXPORTDLG_H #define KEXPORTDLG_H // ---------------------------------------------------------------------------- // QT Headers #include #include // ---------------------------------------------------------------------------- // KDE Headers #include // ---------------------------------------------------------------------------- // Project Headers #include "kmymoneydateinput.h" #include "ui_kexportdlgdecl.h" /** * This class is used to select the required user input to export * a specified account to the popular QIF format. * It relies upon the QIF file handling routines in MyMoneyQifProfile and * MyMoneyQifWriter to do the actual writing of QIF files. * * It uses the global KConfig object to read and write the application * settings. * * @see MyMoneyAccount, MyMoneyQifProfile, MyMoneyQifProfileEditor * * @author Felix Rodriguez, Michael Edwardes, Thomas Baumgart 2000-2003 * * @short A class to select user data required to export a specified account to the popular QIF format. **/ class KExportDlgDecl : public QDialog, public Ui::KExportDlgDecl { public: - KExportDlgDecl(QWidget *parent) : QDialog(parent) { + explicit KExportDlgDecl(QWidget *parent) : QDialog(parent) { setupUi(this); } }; class KExportDlg : public KExportDlgDecl { Q_OBJECT public: - KExportDlg(QWidget *parent); + explicit KExportDlg(QWidget *parent); ~KExportDlg(); /** * This method returns the filename entered into the edit field * * @return QString with filename */ const QString filename() const { return m_qlineeditFile->text(); }; /** * This method returns the account id that has been selected for export * * @return QString with account id */ QString accountId() const; /** * This method returns the name of the profile that has been selected * for the export operation * * @return QString with profile name */ const QString profile() const { return m_profileComboBox->currentText(); }; /** * This method returns the start date of the export dialog */ const QDate startDate() const { return m_kmymoneydateStart->date(); }; /** * This method returns the end date of the export dialog */ const QDate endDate() const { return m_kmymoneydateEnd->date(); }; /** * This method returns the state of the account checkbox */ bool accountSelected() const { return m_qcheckboxAccount->isChecked(); }; /** * This method returns the state of the account checkbox */ bool categorySelected() const { return m_qcheckboxCategories->isChecked(); }; protected slots: /** * Called when the user clicked on the OK button */ void slotOkClicked(); /** * Called when the user needs to browse the filesystem for a QIF file */ void slotBrowse(); /** * This slot checks whether all data is correct to enable * the 'Export' button. The enable state of the 'Export' button * is updated appropriately. * * If the parameter @p account is not empty, then it is assumed * a new account is selected and the date fields will be loaded * with the date of the first and last transaction within this * account. * * @param account The id of the selected account. */ void checkData(const QString& account = QString()); private: void readConfig(); void writeConfig(); /** * This method loads the available profiles into * the combo box. The parameter @p selectLast controls if * the last profile used is preset or not. If preset is not * selected, the current selection remains. If the currently selected * text is not present in the list anymore, the first item will be * selected. * * @param selectLast If true, the last used profile is selected. The * default is false. */ void loadProfiles(const bool selectLast = false); /** * This method is used to load the available accounts into the * combo box for selection. */ void loadAccounts(); /** * This method is used to load an account hierarchy into a string list * * @param strList Reference to the string list to setup * @param id Account id to add * @param leadIn constant leadin to be added in front of the account name */ // void addCategories(QStringList& strList, const QString& id, const QString& leadIn) const; /** * This method is used to return the account id of a given account name * * @param account name of the account * @return the ID of the account will be returned. * See MyMoneyFile::nameToAccount() for details. */ // QString accountId(const QString& account) const; private: QString m_lastAccount; }; #endif diff --git a/kmymoney/plugins/qif/import/kimportdlg.h b/kmymoney/plugins/qif/import/kimportdlg.h index c09e373e2..fe5dd4fc8 100644 --- a/kmymoney/plugins/qif/import/kimportdlg.h +++ b/kmymoney/plugins/qif/import/kimportdlg.h @@ -1,123 +1,123 @@ /*************************************************************************** kimportdlg.h - description ------------------- begin : Wed May 16 2001 copyright : (C) 2001 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KIMPORTDLG_H #define KIMPORTDLG_H // ---------------------------------------------------------------------------- // Qt Headers #include #include #include // ---------------------------------------------------------------------------- // KDE Headers #include // ---------------------------------------------------------------------------- // Project Headers #include "ui_kimportdlgdecl.h" /** * This class is used to import a qif file to an account. * It relies upon the QIF file handling routines in MyMoneyAccount to do * the actual writing of QIF files. * * It uses the global KConfig object to read and write the application * settings. * * @see MyMoneyAccount * * @author Felix Rodriguez, Michael Edwardes 2000-2001 * * @short A class to import a qif file to an account. **/ class KImportDlgDecl : public QDialog, public Ui::KImportDlgDecl { public: - KImportDlgDecl(QWidget *parent) : QDialog(parent) { + explicit KImportDlgDecl(QWidget *parent) : QDialog(parent) { setupUi(this); } }; class KImportDlg : public KImportDlgDecl { Q_OBJECT public: /** * Standard constructor */ - KImportDlg(QWidget *parent); + explicit KImportDlg(QWidget *parent); /** Standard destructor */ ~KImportDlg(); /** */ const QUrl file() const { return QUrl::fromUserInput(m_qlineeditFile->text()); }; /** */ const QString profile() const { return m_profileComboBox->currentText(); }; protected slots: /** Called to let the user browse for a QIF file to import from. */ void slotBrowse(); /** Test whether to enable the buttons */ void slotFileTextChanged(const QString& text); void slotOkClicked(); private: /** * This method loads the available profiles into * the combo box. The parameter @p selectLast controls if * the last profile used is preset or not. If preset is not * selected, the current selection remains. If the currently selected * text is not present in the list anymore, the first item will be * selected. * * @param selectLast If true, the last used profile is selected. The * default is false. */ void loadProfiles(const bool selectLast = false); /** * This method is used to load an account hierarchy into a string list * * @param strList Reference to the string list to setup * @param id Account id to add * @param leadIn constant leadin to be added in front of the account name */ void addCategories(QStringList& strList, const QString& id, const QString& leadIn) const; void readConfig(); void writeConfig(); }; #endif diff --git a/kmymoney/plugins/weboob/dialogs/mapaccount.h b/kmymoney/plugins/weboob/dialogs/mapaccount.h index d65c85ec7..8b732fc79 100644 --- a/kmymoney/plugins/weboob/dialogs/mapaccount.h +++ b/kmymoney/plugins/weboob/dialogs/mapaccount.h @@ -1,64 +1,64 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014-2015 Romain Bignon * Copyright (C) 2014-2015 Florent Fourcot * * 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 WEBOOB_MAPACCOUNT_HPP #define WEBOOB_MAPACCOUNT_HPP #include #include #include "../weboob.h" #include "ui_mapaccount.h" class WbMapAccountDialog : public QWizard, public Ui::WbMapAccountDialog { Q_OBJECT public: Weboob *weboob; - WbMapAccountDialog(QWidget *parent = 0); + explicit WbMapAccountDialog(QWidget *parent = 0); virtual ~WbMapAccountDialog(); protected slots: void checkNextButton(void); void newPage(int id); void gotAccounts(); void gotBackends(); protected: bool finishAccountPage(void); bool finishLoginPage(void); bool finishFiPage(void); private: enum { BACKENDS_PAGE = 0, ACCOUNTS_PAGE }; struct Private; /// \internal d-pointer instance. const std::unique_ptr d; const std::unique_ptr d2; }; #endif /* WEBOOB_MAPACCOUNT_HPP */ diff --git a/kmymoney/plugins/weboob/weboob.h b/kmymoney/plugins/weboob/weboob.h index 96081dd2d..d75ec8fad 100644 --- a/kmymoney/plugins/weboob/weboob.h +++ b/kmymoney/plugins/weboob/weboob.h @@ -1,100 +1,100 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014-2015 Romain Bignon * Copyright (C) 2014-2015 Florent Fourcot * * 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 WEBOOB_HPP #define WEBOOB_HPP #include #include #include #include #include "mymoneymoney.h" class Weboob : public QObject { Q_OBJECT Kross::Action* action; QMutex *mutex; QString path; public: struct Backend { QString name; QString module; }; struct Transaction { QString id; QDate date; QDate rdate; enum type_t { TYPE_UNKNOWN = 0, TYPE_TRANSFER, TYPE_ORDER, TYPE_CHECK, TYPE_DEPOSIT, TYPE_PAYBACK, TYPE_WITHDRAWAL, TYPE_CARD, TYPE_LOAN_PAYMENT, TYPE_BANK } type; QString raw; QString category; QString label; MyMoneyMoney amount; }; struct Account { QString id; QString name; enum type_t { TYPE_UNKNOWN = 0, TYPE_CHECKING, TYPE_SAVINGS, TYPE_DEPOSIT, TYPE_LOAN, TYPE_MARKET, TYPE_JOINT } type; MyMoneyMoney balance; QList transactions; }; - Weboob(QObject* parent = 0); + explicit Weboob(QObject* parent = 0); ~Weboob(); QStringList getProtocols(); QList getBackends(); QList getAccounts(QString backend); Account getAccount(QString backend, QString account, QString max); QVariant execute(QString method, QVariantList args); }; #endif /* WEBOOB_HPP */ diff --git a/kmymoney/reports/kreportchartview.h b/kmymoney/reports/kreportchartview.h index ca5dd6558..0d32034e9 100644 --- a/kmymoney/reports/kreportchartview.h +++ b/kmymoney/reports/kreportchartview.h @@ -1,197 +1,197 @@ /*************************************************************************** kreportchartview.h ------------------- begin : Sat May 22 2004 copyright : (C) 2004-2005 by Ace Jones email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KREPORTCHARTVIEW_H #define KREPORTCHARTVIEW_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "pivotgrid.h" #include "mymoneyreport.h" using namespace KChart; namespace reports { class KReportChartView: public Chart { Q_OBJECT public slots: void slotNeedUpdate(); public: - KReportChartView(QWidget* parent); + explicit KReportChartView(QWidget* parent); ~KReportChartView() {} /** * Returns the labels for the X axis * @see m_abscissaNames */ QStringList& abscissaNames() { return m_abscissaNames; } /** * Draw the chart for a pivot table report */ void drawPivotChart(const PivotGrid &grid, const MyMoneyReport &config, int numberColumns, const QStringList& columnHeadings, const QList& rowTypeList, const QStringList& columnTypeHeaderList); /** * Draw a limit chart * @param limit is either a maximum credit or minimum balance for an account */ void drawLimitLine(const double limit); /** * Remove the chart legend */ void removeLegend(); private: /** * Draw a PivotGridRow in a chart */ int drawPivotGridRow(int rowNum, const PivotGridRow& gridRow, const QString& legendText, const int startColumn = 0, const int columnsToDraw = 0, const int precision = 2); /** * Set cell data */ void setDataCell(int row, int column, const double value, QString tip = QString()); /** * Make sure the model has the right size */ void justifyModelSize(int rows, int columns); /** * Adjust line width of all datasets */ void setLineWidth(const int lineWidth); /** * Set the accountSeries * @see m_accountSeries */ void setAccountSeries(bool accountSeries) { m_accountSeries = accountSeries; } /** * Returns accountSeries * @see m_accountSeries */ bool accountSeries() { return m_accountSeries; } /** * Set the seriesTotals * @see m_seriesTotals */ void setSeriesTotals(bool seriesTotals) { m_seriesTotals = seriesTotals; } /** * Returns accountSeries * @see m_seriesTotals */ bool seriesTotals() { return m_seriesTotals; } /** * Set the number of columns * @see m_numColumns */ void setNumColumns(int numColumns) { m_numColumns = numColumns; } /** * Returns number of columns * @see m_numColumns */ int numColumns() { return m_numColumns; } /** * The labels of the X axis */ QStringList m_abscissaNames; /** * whether series (rows) are accounts (true) or months (false). This causes a lot * of complexity in the charts. The problem is that circular reports work best with * an account in a COLUMN, while line/bar prefer it in a ROW. */ bool m_accountSeries; /** * whether to limit the chart to use series totals only. Used for reports which only * show one dimension (pie) */ bool m_seriesTotals; /** * Number of columns on the report */ int m_numColumns; /** * Model to store chart data */ QStandardItemModel m_model; /** * whether to skip values if zero */ bool m_skipZero; /** * The cached background brush obtained from the style. */ QBrush m_backgroundBrush; /** * The cached foreground brush obtained from the style. */ QBrush m_foregroundBrush; /** * The cached precision obtained from report's data */ int m_precision; }; } // end namespace reports #endif // KREPORTCHARTVIEW_H diff --git a/kmymoney/reports/listtable.h b/kmymoney/reports/listtable.h index b74f4ae18..026100fd9 100644 --- a/kmymoney/reports/listtable.h +++ b/kmymoney/reports/listtable.h @@ -1,155 +1,155 @@ /*************************************************************************** listtable.h ------------------- begin : Sat 28 jun 2008 copyright : (C) 2004-2005 by Ace Jones 2008 by Alvaro Soliverez ****************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef LISTTABLE_H #define LISTTABLE_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "reporttable.h" class MyMoneyReport; namespace reports { class ReportAccount; /** * Calculates a query of information about the transaction database. * * This is a middle-layer class, between the implementing classes and the engine. The * MyMoneyReport class holds only the CONFIGURATION parameters. This * class has some common methods used by querytable and objectinfo classes * * @author Alvaro Soliverez * * @short **/ class ListTable : public ReportTable { public: - ListTable(const MyMoneyReport&); + explicit ListTable(const MyMoneyReport&); QString renderHTML() const; QString renderCSV() const; void drawChart(KReportChartView&) const {} void dump(const QString& file, const QString& context = QString()) const; void init(); public: enum cellTypeE /*{*/ /*Money*/ {ctValue, ctNetInvValue, ctMarketValue, ctPrice, ctLastPrice, ctBuyPrice, ctBuys, ctSells, ctBuysST, ctSellsST, ctBuysLT, ctSellsLT, ctCapitalGain, ctCapitalGainST,ctCapitalGainLT, ctCashIncome, ctReinvestIncome, ctFees, ctInterest, ctStartingBalance, ctEndingBalance, ctBalance, ctCurrentBalance, ctBalanceWarning, ctMaxBalanceLimit, ctOpeningBalance, ctCreditWarning, ctMaxCreditLimit, ctLoanAmount, ctPeriodicPayment, ctFinalPayment, ctPayment, /*Shares*/ ctShares, /*Percent*/ ctReturn, ctReturnInvestment, ctInterestRate, ctPercentageGain, /*Date*/ ctPostDate, ctEntryDate, ctNextDueDate, ctOpeningDate, ctNextInterestChange, ctMonth, ctWeek, ctReconcileDate, /*Misc*/ ctCurrency, ctCurrencyName, ctCommodity, ctID, ctRank, ctSplit, ctMemo, ctAccount, ctAccountID, ctTopAccount, ctInvestAccount, ctInstitution, ctCategory, ctTopCategory, ctCategoryType, ctNumber, ctReconcileFlag, ctAction, ctTag, ctPayee, ctEquityType, ctType, ctName, ctDepth, ctRowsCount, ctTax, ctFavorite, ctDescription, ctOccurence, ctPaymentType }; /** * Contains a single row in the table. * * Each column is a key/value pair, both strings. This class is just * a QMap with the added ability to specify which columns you'd like to * use as a sort key when you qHeapSort a list of these TableRows */ class TableRow: public QMap { public: bool operator< (const TableRow&) const; bool operator<= (const TableRow&) const; bool operator> (const TableRow&) const; bool operator== (const TableRow&) const; static void setSortCriteria(const QVector& _criteria) { m_sortCriteria = _criteria; } private: static QVector m_sortCriteria; }; const QList& rows() { return m_rows; } protected: void render(QString&, QString&) const; /** * If not in expert mode, include all subaccounts for each selected * investment account. * For investment-only reports, it will also exclude the subaccounts * that have a zero balance */ void includeInvestmentSubAccounts(); QList m_rows; QList m_group; /** * Comma-separated list of columns to place BEFORE the subtotal column */ QList m_columns; /** * Name of the subtotal column */ QList m_subtotal; /** * Comma-separated list of columns to place AFTER the subtotal column */ QList m_postcolumns; private: enum cellGroupE { cgMoney, cgShares, cgPercent, cgDate, cgPrice, cgMisc }; static cellGroupE cellGroup(const cellTypeE cellType); static QString tableHeader(const cellTypeE cellType); }; } #endif diff --git a/kmymoney/reports/objectinfotable.cpp b/kmymoney/reports/objectinfotable.cpp index d6e04b406..4a5c4e113 100644 --- a/kmymoney/reports/objectinfotable.cpp +++ b/kmymoney/reports/objectinfotable.cpp @@ -1,375 +1,375 @@ /*************************************************************************** objectinfotable.cpp ------------------- begin : Sat 28 jun 2008 copyright : (C) 2004-2005 by Ace Jones 2008 by Alvaro Soliverez ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "objectinfotable.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneyaccountloan.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneypayee.h" #include "mymoneymoney.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyreport.h" #include "mymoneyschedule.h" #include "mymoneyexception.h" #include "kmymoneyutils.h" #include "reportaccount.h" #include "mymoneyenums.h" namespace reports { // **************************************************************************** // // ObjectInfoTable implementation // // **************************************************************************** /** * TODO * * - Collapse 2- & 3- groups when they are identical * - Way more test cases (especially splits & transfers) * - Option to collapse splits * - Option to exclude transfers * */ ObjectInfoTable::ObjectInfoTable(const MyMoneyReport& _report): ListTable(_report) { // separated into its own method to allow debugging (setting breakpoints // directly in ctors somehow does not work for me (ipwizard)) // TODO: remove the init() method and move the code back to the ctor init(); } void ObjectInfoTable::init() { m_columns.clear(); m_group.clear(); m_subtotal.clear(); switch (m_config.rowType()) { case MyMoneyReport::eSchedule: constructScheduleTable(); m_columns << ctNextDueDate << ctName; break; case MyMoneyReport::eAccountInfo: constructAccountTable(); m_columns << ctInstitution << ctType << ctName; break; case MyMoneyReport::eAccountLoanInfo: constructAccountLoanTable(); m_columns << ctInstitution << ctType << ctName; break; default: break; } // Sort the data to match the report definition m_subtotal << ctValue; switch (m_config.rowType()) { case MyMoneyReport::eSchedule: m_group << ctType; m_subtotal << ctValue; break; case MyMoneyReport::eAccountInfo: case MyMoneyReport::eAccountLoanInfo: m_group << ctTopCategory << ctInstitution; m_subtotal << ctCurrentBalance; break; default: throw MYMONEYEXCEPTION("ObjectInfoTable::ObjectInfoTable(): unhandled row type"); } QVector sort = QVector::fromList(m_group) << QVector::fromList(m_columns) << ctID << ctRank; switch (m_config.rowType()) { case MyMoneyReport::eSchedule: if (m_config.detailLevel() == MyMoneyReport::eDetailAll) { m_columns << ctName << ctPayee << ctPaymentType << ctOccurence << ctNextDueDate << ctCategory; // krazy:exclude=spelling } else { m_columns << ctName << ctPayee << ctPaymentType << ctOccurence << ctNextDueDate; // krazy:exclude=spelling } break; case MyMoneyReport::eAccountInfo: m_columns << ctType << ctName << ctNumber << ctDescription << ctOpeningDate << ctCurrencyName << ctBalanceWarning << ctCreditWarning << ctMaxCreditLimit << ctTax << ctFavorite; break; case MyMoneyReport::eAccountLoanInfo: m_columns << ctType << ctName << ctNumber << ctDescription << ctOpeningDate << ctCurrencyName << ctPayee << ctLoanAmount << ctInterestRate << ctNextInterestChange << ctPeriodicPayment << ctFinalPayment << ctFavorite; break; default: m_columns.clear(); } TableRow::setSortCriteria(sort); qSort(m_rows); } void ObjectInfoTable::constructScheduleTable() { MyMoneyFile* file = MyMoneyFile::instance(); QList schedules; schedules = file->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, m_config.fromDate(), m_config.toDate(), false); QList::const_iterator it_schedule = schedules.constBegin(); while (it_schedule != schedules.constEnd()) { MyMoneySchedule schedule = *it_schedule; - ReportAccount account = schedule.account(); + ReportAccount account(schedule.account()); if (m_config.includes(account)) { //get fraction for account int fraction = account.fraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction(); TableRow scheduleRow; //convert to base currency if needed MyMoneyMoney xr = MyMoneyMoney::ONE; if (m_config.isConvertCurrency() && account.isForeignCurrency()) { xr = account.baseCurrencyPrice(QDate::currentDate()).reduce(); } // help for sort and render functions scheduleRow[ctRank] = QLatin1Char('0'); //schedule data scheduleRow[ctID] = schedule.id(); scheduleRow[ctName] = schedule.name(); scheduleRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate); scheduleRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); scheduleRow[ctOccurence] = i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1()); // krazy:exclude=spelling scheduleRow[ctPaymentType] = KMyMoneyUtils::paymentMethodToString(schedule.paymentType()); //scheduleRow["category"] = account.name(); //to get the payee we must look into the splits of the transaction MyMoneyTransaction transaction = schedule.transaction(); MyMoneySplit split = transaction.splitByAccount(account.id(), true); scheduleRow[ctValue] = (split.value() * xr).toString(); MyMoneyPayee payee = file->payee(split.payeeId()); scheduleRow[ctPayee] = payee.name(); m_rows += scheduleRow; //the text matches the main split bool transaction_text = m_config.match(&split); if (m_config.detailLevel() == MyMoneyReport::eDetailAll) { //get the information for all splits QList splits = transaction.splits(); QList::const_iterator split_it = splits.constBegin(); for (; split_it != splits.constEnd(); ++split_it) { TableRow splitRow; - ReportAccount splitAcc = (*split_it).accountId(); + ReportAccount splitAcc((*split_it).accountId()); splitRow[ctRank] = QLatin1Char('1'); splitRow[ctID] = schedule.id(); splitRow[ctName] = schedule.name(); splitRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); splitRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate); if ((*split_it).value() == MyMoneyMoney::autoCalc) { splitRow[ctSplit] = MyMoneyMoney::autoCalc.toString(); } else if (! splitAcc.isIncomeExpense()) { splitRow[ctSplit] = (*split_it).value().toString(); } else { splitRow[ctSplit] = (- (*split_it).value()).toString(); } //if it is an assett account, mark it as a transfer if (! splitAcc.isIncomeExpense()) { splitRow[ctCategory] = ((* split_it).value().isNegative()) ? i18n("Transfer from %1" , splitAcc.fullName()) : i18n("Transfer to %1" , splitAcc.fullName()); } else { splitRow [ctCategory] = splitAcc.fullName(); } //add the split only if it matches the text or it matches the main split if (m_config.match(&(*split_it)) || transaction_text) m_rows += splitRow; } } } ++it_schedule; } } void ObjectInfoTable::constructAccountTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { TableRow accountRow; - ReportAccount account = *it_account; + ReportAccount account(*it_account); if (m_config.includes(account) && account.accountType() != eMyMoney::Account::Type::Stock && !account.isClosed()) { MyMoneyMoney value; accountRow[ctRank] = QLatin1Char('0'); accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup()); accountRow[ctInstitution] = (file->institution(account.institutionId())).name(); accountRow[ctType] = MyMoneyAccount::accountTypeToString(account.accountType()); accountRow[ctName] = account.name(); accountRow[ctNumber] = account.number(); accountRow[ctDescription] = account.description(); accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate); //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol(); accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name(); accountRow[ctBalanceWarning] = account.value("minBalanceEarly"); accountRow[ctMaxBalanceLimit] = account.value("minBalanceAbsolute"); accountRow[ctCreditWarning] = account.value("maxCreditEarly"); accountRow[ctMaxCreditLimit] = account.value("maxCreditAbsolute"); accountRow[ctTax] = account.value("Tax") == QLatin1String("Yes") ? i18nc("Is this a tax account?", "Yes") : QString(); accountRow[ctOpeningBalance] = account.value("OpeningBalanceAccount") == QLatin1String("Yes") ? i18nc("Is this an opening balance account?", "Yes") : QString(); accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString(); //investment accounts show the balances of all its subaccounts if (account.accountType() == eMyMoney::Account::Type::Investment) { value = investmentBalance(account); } else { value = file->balance(account.id()); } //convert to base currency if needed if (m_config.isConvertCurrency() && account.isForeignCurrency()) { MyMoneyMoney xr = account.baseCurrencyPrice(QDate::currentDate()).reduce(); value = value * xr; } accountRow[ctCurrentBalance] = value.toString(); m_rows += accountRow; } ++it_account; } } void ObjectInfoTable::constructAccountLoanTable() { MyMoneyFile* file = MyMoneyFile::instance(); QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { TableRow accountRow; - ReportAccount account = *it_account; + ReportAccount account(*it_account); MyMoneyAccountLoan loan = *it_account; if (m_config.includes(account) && account.isLoan() && !account.isClosed()) { //convert to base currency if needed MyMoneyMoney xr = MyMoneyMoney::ONE; if (m_config.isConvertCurrency() && account.isForeignCurrency()) { xr = account.baseCurrencyPrice(QDate::currentDate()).reduce(); } accountRow[ctRank] = QLatin1Char('0'); accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup()); accountRow[ctInstitution] = (file->institution(account.institutionId())).name(); accountRow[ctType] = MyMoneyAccount::accountTypeToString(account.accountType()); accountRow[ctName] = account.name(); accountRow[ctNumber] = account.number(); accountRow[ctDescription] = account.description(); accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate); //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol(); accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name(); accountRow[ctPayee] = file->payee(loan.payee()).name(); accountRow[ctLoanAmount] = (loan.loanAmount() * xr).toString(); accountRow[ctInterestRate] = (loan.interestRate(QDate::currentDate()) / MyMoneyMoney(100, 1) * xr).toString(); accountRow[ctNextInterestChange] = loan.nextInterestChange().toString(Qt::ISODate); accountRow[ctPeriodicPayment] = (loan.periodicPayment() * xr).toString(); accountRow[ctFinalPayment] = (loan.finalPayment() * xr).toString(); accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString(); MyMoneyMoney value = file->balance(account.id()); value = value * xr; accountRow[ctCurrentBalance] = value.toString(); m_rows += accountRow; } ++it_account; } } MyMoneyMoney ObjectInfoTable::investmentBalance(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyMoney value = file->balance(acc.id()); QStringList accList = acc.accountList(); QStringList::const_iterator it_a = accList.constBegin(); for (; it_a != acc.accountList().constEnd(); ++it_a) { MyMoneyAccount stock = file->account(*it_a); try { MyMoneyMoney val; MyMoneyMoney balance = file->balance(stock.id()); MyMoneySecurity security = file->security(stock.currencyId()); const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency()); val = balance * price.rate(security.tradingCurrency()); // adjust value of security to the currency of the account MyMoneySecurity accountCurrency = file->currency(acc.currencyId()); val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id()); val = val.convert(acc.fraction()); value += val; } catch (const MyMoneyException &e) { qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what()))); } } return value; } } diff --git a/kmymoney/reports/objectinfotable.h b/kmymoney/reports/objectinfotable.h index a12a29a30..bfa221769 100644 --- a/kmymoney/reports/objectinfotable.h +++ b/kmymoney/reports/objectinfotable.h @@ -1,74 +1,74 @@ /*************************************************************************** objectinfotable.h ------------------- begin : Sat 28 jun 2008 copyright : (C) 2004-2005 by Ace Jones (C) 2008 by Alvaro Soliverez ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef OBJECTINFOTABLE_H #define OBJECTINFOTABLE_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "listtable.h" class MyMoneyReport; namespace reports { class ReportAccount; /** * Calculates a query of information about the transaction database. * * This is a middle-layer class, between the UI and the engine. The * MyMoneyReport class holds only the CONFIGURATION parameters. This * class actually does the work of retrieving the data from the engine * and formatting it for the user. * * @author Ace Jones * * @short **/ class ObjectInfoTable : public ListTable { public: - ObjectInfoTable(const MyMoneyReport&); + explicit ObjectInfoTable(const MyMoneyReport&); void init(); protected: void constructScheduleTable(); void constructAccountTable(); void constructAccountLoanTable(); private: /** * @param acc the investment account * @return the balance in the currency of the investment account */ MyMoneyMoney investmentBalance(const MyMoneyAccount& acc); }; } #endif // QUERYREPORT_H diff --git a/kmymoney/reports/pivotgrid.h b/kmymoney/reports/pivotgrid.h index 8987beba2..c67a864e3 100644 --- a/kmymoney/reports/pivotgrid.h +++ b/kmymoney/reports/pivotgrid.h @@ -1,155 +1,155 @@ /*************************************************************************** pivotgrid.h ------------------- begin : Sat May 22 2004 copyright : (C) 2004-2005 by Ace Jones email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef PIVOTGRID_H #define PIVOTGRID_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "reportaccount.h" #include "mymoneymoney.h" namespace reports { enum ERowType {eActual, eBudget, eBudgetDiff, eForecast, eAverage, ePrice }; /** * The fundamental data construct of this class is a 'grid'. It is organized as follows: * * A 'Row' is a row of money values, each column is a month. The first month corresponds to * m_beginDate. * * A 'Row Pair' is two rows of money values. Each column is the SAME month. One row is the * 'actual' values for the period, the other row is the 'budgetted' values for the same * period. For ease of implementation, a Row Pair is implemented as a Row which contains * another Row. The inherited Row is the 'actual', the contained row is the 'Budget'. * * An 'Inner Group' contains a rows for each subordinate account within a single top-level * account. It also contains a mapping from the account descriptor for the subordinate account * to its row data. So if we have an Expense account called "Computers", with sub-accounts called * "Hardware", "Software", and "Peripherals", there will be one Inner Group for "Computers" * which contains three Rows. * * An 'Outer Group' contains Inner Row Groups for all the top-level accounts in a given * account class. Account classes are Expense, Income, Asset, Liability. In the case above, * the "Computers" Inner Group is contained within the "Expense" Outer Group. * * A 'Grid' is the set of all Outer Groups contained in this report. * */ class PivotCell: public MyMoneyMoney { KMM_MYMONEY_UNIT_TESTABLE public: PivotCell() : m_stockSplit(MyMoneyMoney::ONE), m_cellUsed(false) {} - PivotCell(const MyMoneyMoney& value); + explicit PivotCell(const MyMoneyMoney& value); virtual ~PivotCell(); static PivotCell stockSplit(const MyMoneyMoney& factor); PivotCell operator += (const PivotCell& right); PivotCell operator += (const MyMoneyMoney& value); const QString formatMoney(int fraction, bool showThousandSeparator = true) const; const QString formatMoney(const QString& currency, const int prec, bool showThousandSeparator = true) const; MyMoneyMoney calculateRunningSum(const MyMoneyMoney& runningSum); MyMoneyMoney cellBalance(const MyMoneyMoney& _balance); bool isUsed() const { return m_cellUsed; } private: MyMoneyMoney m_stockSplit; MyMoneyMoney m_postSplit; bool m_cellUsed; }; class PivotGridRow: public QList { public: - PivotGridRow(unsigned _numcolumns = 0) { + explicit PivotGridRow(unsigned _numcolumns = 0) { for (uint i = 0; i < _numcolumns; i++) append(PivotCell()); } MyMoneyMoney m_total; }; class PivotGridRowSet: public QMap { public: - PivotGridRowSet(unsigned _numcolumns = 0); + explicit PivotGridRowSet(unsigned _numcolumns = 0); }; class PivotInnerGroup: public QMap { public: - PivotInnerGroup(unsigned _numcolumns = 0): m_total(_numcolumns) {} + explicit PivotInnerGroup(unsigned _numcolumns = 0): m_total(_numcolumns) {} PivotGridRowSet m_total; }; class PivotOuterGroup: public QMap { public: explicit PivotOuterGroup(unsigned _numcolumns = 0, unsigned _sort = m_kDefaultSortOrder, bool _inverted = false): m_total(_numcolumns), m_inverted(_inverted), m_sortOrder(_sort) {} bool operator<(const PivotOuterGroup& _right) const { if (m_sortOrder != _right.m_sortOrder) return m_sortOrder < _right.m_sortOrder; else return m_displayName < _right.m_displayName; } PivotGridRowSet m_total; // An inverted outergroup means that all values placed in subordinate rows // should have their sign inverted from typical cash-flow notation. Also it // means that when the report is summed, the values should be inverted again // so that the grand total is really "non-inverted outergroup MINUS inverted outergroup". bool m_inverted; // The localized name of the group for display in the report. Outergoups need this // independently, because they will lose their association with the TGrid when the // report is rendered. QString m_displayName; // lower numbers sort toward the top of the report. defaults to 100, which is a nice // middle-of-the-road value unsigned m_sortOrder; // default sort order static const unsigned m_kDefaultSortOrder; }; class PivotGrid: public QMap { public: PivotGridRowSet rowSet(QString id); PivotGridRowSet m_total; }; } #endif // PIVOTGRID_H diff --git a/kmymoney/reports/pivottable.cpp b/kmymoney/reports/pivottable.cpp index 020c9bf5b..e6b740ea0 100644 --- a/kmymoney/reports/pivottable.cpp +++ b/kmymoney/reports/pivottable.cpp @@ -1,2344 +1,2344 @@ /*************************************************************************** pivottable.cpp ------------------- begin : Mon May 17 2004 copyright : (C) 2004-2005 by Ace Jones email : Thomas Baumgart Alvaro Soliverez ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "pivottable.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "pivotgrid.h" #include "reportdebug.h" #include "kreportchartview.h" #include "kmymoneyglobalsettings.h" #include "kmymoneyutils.h" #include "mymoneyforecast.h" #include "mymoneyprice.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneybudget.h" #include "mymoneyreport.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" namespace KChart { class Widget; } namespace reports { using KChart::Widget; QString Debug::m_sTabs; bool Debug::m_sEnabled = DEBUG_ENABLED_BY_DEFAULT; QString Debug::m_sEnableKey; Debug::Debug(const QString& _name): m_methodName(_name), m_enabled(m_sEnabled) { if (!m_enabled && _name == m_sEnableKey) m_enabled = true; if (m_enabled) { qDebug("%s%s(): ENTER", qPrintable(m_sTabs), qPrintable(m_methodName)); m_sTabs.append("--"); } } Debug::~Debug() { if (m_enabled) { m_sTabs.remove(0, 2); qDebug("%s%s(): EXIT", qPrintable(m_sTabs), qPrintable(m_methodName)); if (m_methodName == m_sEnableKey) m_enabled = false; } } void Debug::output(const QString& _text) { if (m_enabled) qDebug("%s%s(): %s", qPrintable(m_sTabs), qPrintable(m_methodName), qPrintable(_text)); } PivotTable::PivotTable(const MyMoneyReport& _report): ReportTable(_report), m_runningSumsCalculated(false) { init(); } void PivotTable::init() { DEBUG_ENTER(Q_FUNC_INFO); // // Initialize locals // MyMoneyFile* file = MyMoneyFile::instance(); // // Initialize member variables // //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); m_config.validDateRange(m_beginDate, m_endDate); // If we need to calculate running sums, it does not make sense // to show a row total column if (m_config.isRunningSum()) m_config.setShowingRowTotals(false); if (m_config.isRunningSum() && !m_config.isIncludingPrice() && !m_config.isIncludingAveragePrice() && !m_config.isIncludingMovingAverage()) m_startColumn = 1; else m_startColumn = 0; m_numColumns = columnValue(m_endDate) - columnValue(m_beginDate) + 1 + m_startColumn; // 1 for m_beginDate values and m_startColumn for opening balance values //Load what types of row the report is going to show loadRowTypeList(); // // Initialize outer groups of the grid // if (m_config.rowType() == MyMoneyReport::eAssetLiability) { m_grid.insert(MyMoneyAccount::accountTypeToString(eMyMoney::Account::Type::Asset), PivotOuterGroup(m_numColumns)); m_grid.insert(MyMoneyAccount::accountTypeToString(eMyMoney::Account::Type::Liability), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder, true /* inverted */)); } else { m_grid.insert(MyMoneyAccount::accountTypeToString(eMyMoney::Account::Type::Income), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 2)); m_grid.insert(MyMoneyAccount::accountTypeToString(eMyMoney::Account::Type::Expense), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 1, true /* inverted */)); // // Create rows for income/expense reports with all accounts included // if (m_config.isIncludingUnusedAccounts()) createAccountRows(); } // // Initialize grid totals // m_grid.m_total = PivotGridRowSet(m_numColumns); // // Get opening balances // Only net worth report qualifies if (m_startColumn == 1) calculateOpeningBalances(); // // Calculate budget mapping // (for budget reports only) // if (m_config.hasBudget()) calculateBudgetMapping(); // prices report doesn't need transactions, but it needs account stub // otherwise fillBasePriceUnit won't do nothing if (m_config.isIncludingPrice() || m_config.isIncludingAveragePrice()) { QList accounts; file->accountList(accounts); foreach (const auto acc, accounts) { if (acc.isInvest()) { - const ReportAccount repAcc = acc; + const ReportAccount repAcc(acc); if (m_config.includes(repAcc)) { const auto outergroup = acc.accountTypeToString(acc.accountType()); assignCell(outergroup, repAcc, 0, MyMoneyMoney(), false, false); // add account stub } } } } else { // // Populate all transactions into the row/column pivot grid // QList transactions; m_config.setReportAllSplits(false); m_config.setConsiderCategory(true); try { transactions = file->transactionList(m_config); } catch (const MyMoneyException &e) { qDebug("ERR: %s thrown in %s(%ld)", qPrintable(e.what()), qPrintable(e.file()), e.line()); throw e; } DEBUG_OUTPUT(QString("Found %1 matching transactions").arg(transactions.count())); // Include scheduled transactions if required if (m_config.isIncludingSchedules()) { // Create a custom version of the report filter, excluding date // We'll use this to compare the transaction against MyMoneyTransactionFilter schedulefilter(m_config); schedulefilter.setDateFilter(QDate(), QDate()); // Get the real dates from the config filter QDate configbegin, configend; m_config.validDateRange(configbegin, configend); QList schedules = file->scheduleList(); QList::const_iterator it_schedule = schedules.constBegin(); while (it_schedule != schedules.constEnd()) { // If the transaction meets the filter MyMoneyTransaction tx = (*it_schedule).transaction(); if (!(*it_schedule).isFinished() && schedulefilter.match(tx)) { // Keep the id of the schedule with the transaction so that // we can do the autocalc later on in case of a loan payment tx.setValue("kmm-schedule-id", (*it_schedule).id()); // Get the dates when a payment will be made within the report window QDate nextpayment = (*it_schedule).adjustedNextPayment(configbegin); if (nextpayment.isValid()) { // Add one transaction for each date QList paymentDates = (*it_schedule).paymentDates(nextpayment, configend); QList::const_iterator it_date = paymentDates.constBegin(); while (it_date != paymentDates.constEnd()) { //if the payment occurs in the past, enter it tomorrow if (QDate::currentDate() >= *it_date) { tx.setPostDate(QDate::currentDate().addDays(1)); } else { tx.setPostDate(*it_date); } if (tx.postDate() <= configend && tx.postDate() >= configbegin) { transactions += tx; } DEBUG_OUTPUT(QString("Added transaction for schedule %1 on %2").arg((*it_schedule).id()).arg((*it_date).toString())); ++it_date; } } } ++it_schedule; } } // whether asset & liability transactions are actually to be considered // transfers bool al_transfers = (m_config.rowType() == MyMoneyReport::eExpenseIncome) && (m_config.isIncludingTransfers()); //this is to store balance for loan accounts when not included in the report QMap loanBalances; QList::const_iterator it_transaction = transactions.constBegin(); int colofs = columnValue(m_beginDate) - m_startColumn; while (it_transaction != transactions.constEnd()) { MyMoneyTransaction tx = (*it_transaction); QDate postdate = tx.postDate(); if (postdate < m_beginDate) { qDebug("MyMoneyFile::transactionList returned a transaction that is outside the date filter, skipping it"); ++it_transaction; continue; } int column = columnValue(postdate) - colofs; // check if we need to call the autocalculation routine if (tx.isLoanPayment() && tx.hasAutoCalcSplit() && (tx.value("kmm-schedule-id").length() > 0)) { // make sure to consider any autocalculation for loan payments MyMoneySchedule sched = file->schedule(tx.value("kmm-schedule-id")); const MyMoneySplit& split = tx.amortizationSplit(); if (!split.id().isEmpty()) { - ReportAccount splitAccount = file->account(split.accountId()); + ReportAccount splitAccount(file->account(split.accountId())); eMyMoney::Account::Type type = splitAccount.accountGroup(); QString outergroup = MyMoneyAccount::accountTypeToString(type); //if the account is included in the report, calculate the balance from the cells if (m_config.includes(splitAccount)) { loanBalances[splitAccount.id()] = cellBalance(outergroup, splitAccount, column, false); } else { //if it is not in the report and also not in loanBalances, get the balance from the file if (!loanBalances.contains(splitAccount.id())) { QDate dueDate = sched.nextDueDate(); //if the payment is overdue, use current date if (dueDate < QDate::currentDate()) dueDate = QDate::currentDate(); //get the balance from the file for the date loanBalances[splitAccount.id()] = file->balance(splitAccount.id(), dueDate.addDays(-1)); } } KMyMoneyUtils::calculateAutoLoan(sched, tx, loanBalances); //if the loan split is not included in the report, update the balance for the next occurrence if (!m_config.includes(splitAccount)) { QList::ConstIterator it_loanSplits; for (it_loanSplits = tx.splits().constBegin(); it_loanSplits != tx.splits().constEnd(); ++it_loanSplits) { if ((*it_loanSplits).isAmortizationSplit() && (*it_loanSplits).accountId() == splitAccount.id()) loanBalances[splitAccount.id()] = loanBalances[splitAccount.id()] + (*it_loanSplits).shares(); } } } } QList splits = tx.splits(); QList::const_iterator it_split = splits.constBegin(); while (it_split != splits.constEnd()) { - ReportAccount splitAccount = (*it_split).accountId(); + ReportAccount splitAccount((*it_split).accountId()); // Each split must be further filtered, because if even one split matches, // the ENTIRE transaction is returned with all splits (even non-matching ones) if (m_config.includes(splitAccount) && m_config.match(&(*it_split))) { // reverse sign to match common notation for cash flow direction, only for expense/income splits MyMoneyMoney reverse(splitAccount.isIncomeExpense() ? -1 : 1, 1); MyMoneyMoney value; // the outer group is the account class (major account type) eMyMoney::Account::Type type = splitAccount.accountGroup(); QString outergroup = MyMoneyAccount::accountTypeToString(type); value = (*it_split).shares(); bool stockSplit = tx.isStockSplit(); if (!stockSplit) { // retrieve the value in the account's underlying currency if (value != MyMoneyMoney::autoCalc) { value = value * reverse; } else { qDebug("PivotTable::PivotTable(): This must not happen"); value = MyMoneyMoney(); // keep it 0 so far } // Except in the case of transfers on an income/expense report if (al_transfers && (type == eMyMoney::Account::Type::Asset || type == eMyMoney::Account::Type::Liability)) { outergroup = i18n("Transfers"); value = -value; } } // add the value to its correct position in the pivot table assignCell(outergroup, splitAccount, column, value, false, stockSplit); } ++it_split; } ++it_transaction; } } // // Get forecast data // if (m_config.isIncludingForecast()) calculateForecast(); // //Insert Price data // if (m_config.isIncludingPrice()) fillBasePriceUnit(ePrice); // //Insert Average Price data // if (m_config.isIncludingAveragePrice()) { fillBasePriceUnit(eActual); calculateMovingAverage(); } // // Collapse columns to match column type // if (m_config.columnPitch() > 1) collapseColumns(); // // Calculate the running sums // (for running sum reports only) // if (m_config.isRunningSum()) calculateRunningSums(); // // Calculate Moving Average // if (m_config.isIncludingMovingAverage()) calculateMovingAverage(); // // Calculate Budget Difference // if (m_config.isIncludingBudgetActuals()) calculateBudgetDiff(); // // Convert all values to the deep currency // convertToDeepCurrency(); // // Convert all values to the base currency // if (m_config.isConvertCurrency()) convertToBaseCurrency(); // // Determine column headings // calculateColumnHeadings(); // // Calculate row and column totals // calculateTotals(); // // If using mixed time, calculate column for current date // m_config.setCurrentDateColumn(currentDateColumn()); } void PivotTable::collapseColumns() { DEBUG_ENTER(Q_FUNC_INFO); int columnpitch = m_config.columnPitch(); if (columnpitch != 1) { int sourcemonth = (m_config.isColumnsAreDays()) // use the user's locale to determine the week's start ? (m_beginDate.dayOfWeek() + 8 - QLocale().firstDayOfWeek()) % 7 : m_beginDate.month(); int sourcecolumn = m_startColumn; int destcolumn = m_startColumn; while (sourcecolumn < m_numColumns) { if (sourcecolumn != destcolumn) { #if 0 // TODO: Clean up this rather inefficient kludge. We really should jump by an entire // destcolumn at a time on RS reports, and calculate the proper sourcecolumn to use, // allowing us to clear and accumulate only ONCE per destcolumn if (m_config_f.isRunningSum()) clearColumn(destcolumn); #endif accumulateColumn(destcolumn, sourcecolumn); } if (++sourcecolumn < m_numColumns) { if ((sourcemonth++ % columnpitch) == 0) { if (sourcecolumn != ++destcolumn) clearColumn(destcolumn); } } } m_numColumns = destcolumn + 1; } } void PivotTable::accumulateColumn(int destcolumn, int sourcecolumn) { DEBUG_ENTER(Q_FUNC_INFO); DEBUG_OUTPUT(QString("From Column %1 to %2").arg(sourcecolumn).arg(destcolumn)); // iterate over outer groups PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { // iterate over inner groups PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { // iterator over rows PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { if ((*it_row)[eActual].count() <= sourcecolumn) throw MYMONEYEXCEPTION(QString("Sourcecolumn %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(sourcecolumn).arg((*it_row)[eActual].count())); if ((*it_row)[eActual].count() <= destcolumn) throw MYMONEYEXCEPTION(QString("Destcolumn %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(sourcecolumn).arg((*it_row)[eActual].count())); (*it_row)[eActual][destcolumn] += (*it_row)[eActual][sourcecolumn]; ++it_row; } ++it_innergroup; } ++it_outergroup; } } void PivotTable::clearColumn(int column) { DEBUG_ENTER(Q_FUNC_INFO); DEBUG_OUTPUT(QString("Column %1").arg(column)); // iterate over outer groups PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { // iterate over inner groups PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { // iterator over rows PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { if ((*it_row)[eActual].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::accumulateColumn").arg(column).arg((*it_row)[eActual].count())); (*it_row++)[eActual][column] = PivotCell(); } ++it_innergroup; } ++it_outergroup; } } void PivotTable::calculateColumnHeadings() { DEBUG_ENTER(Q_FUNC_INFO); // one column for the opening balance if (m_startColumn == 1) m_columnHeadings.append("Opening"); int columnpitch = m_config.columnPitch(); if (columnpitch == 0) { // output the warning but don't crash by dividing with 0 qWarning("PivotTable::calculateColumnHeadings() Invalid column pitch"); return; } // if this is a days-based report if (m_config.isColumnsAreDays()) { if (columnpitch == 1) { QDate columnDate = m_beginDate; int column = m_startColumn; while (column++ < m_numColumns) { QString heading = QLocale().monthName(columnDate.month(), QLocale::ShortFormat) + ' ' + QString::number(columnDate.day()); columnDate = columnDate.addDays(1); m_columnHeadings.append(heading); } } else { QDate day = m_beginDate; QDate prv = m_beginDate; // use the user's locale to determine the week's start int dow = (day.dayOfWeek() + 8 - QLocale().firstDayOfWeek()) % 7; while (day <= m_endDate) { if (((dow % columnpitch) == 0) || (day == m_endDate)) { m_columnHeadings.append(QString("%1 %2 - %3 %4") .arg(QLocale().monthName(prv.month(), QLocale::ShortFormat)) .arg(prv.day()) .arg(QLocale().monthName(day.month(), QLocale::ShortFormat)) .arg(day.day())); prv = day.addDays(1); } day = day.addDays(1); dow++; } } } // else it's a months-based report else { if (columnpitch == 12) { int year = m_beginDate.year(); int column = m_startColumn; while (column++ < m_numColumns) m_columnHeadings.append(QString::number(year++)); } else { int year = m_beginDate.year(); bool includeyear = (m_beginDate.year() != m_endDate.year()); int segment = (m_beginDate.month() - 1) / columnpitch; int column = m_startColumn; while (column++ < m_numColumns) { QString heading = QLocale().monthName(1 + segment * columnpitch, QLocale::ShortFormat); if (columnpitch != 1) heading += '-' + QLocale().monthName((1 + segment) * columnpitch, QLocale::ShortFormat); if (includeyear) heading += ' ' + QString::number(year); m_columnHeadings.append(heading); if (++segment >= 12 / columnpitch) { segment -= 12 / columnpitch; ++year; } } } } } void PivotTable::createAccountRows() { DEBUG_ENTER(Q_FUNC_INFO); MyMoneyFile* file = MyMoneyFile::instance(); QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { - ReportAccount account = *it_account; + ReportAccount account(*it_account); // only include this item if its account group is included in this report // and if the report includes this account if (m_config.includes(*it_account)) { DEBUG_OUTPUT(QString("Includes account %1").arg(account.name())); // the row group is the account class (major account type) QString outergroup = MyMoneyAccount::accountTypeToString(account.accountGroup()); // place into the 'opening' column... assignCell(outergroup, account, 0, MyMoneyMoney()); } ++it_account; } } void PivotTable::calculateOpeningBalances() { DEBUG_ENTER(Q_FUNC_INFO); // First, determine the inclusive dates of the report. Normally, that's just // the begin & end dates of m_config_f. However, if either of those dates are // blank, we need to use m_beginDate and/or m_endDate instead. QDate from = m_config.fromDate(); QDate to = m_config.toDate(); if (! from.isValid()) from = m_beginDate; if (! to.isValid()) to = m_endDate; MyMoneyFile* file = MyMoneyFile::instance(); QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { - ReportAccount account = *it_account; + ReportAccount account(*it_account); // only include this item if its account group is included in this report // and if the report includes this account if (m_config.includes(*it_account)) { //do not include account if it is closed and it has no transactions in the report period if (account.isClosed()) { //check if the account has transactions for the report timeframe MyMoneyTransactionFilter filter; filter.addAccount(account.id()); filter.setDateFilter(m_beginDate, m_endDate); filter.setReportAllSplits(false); QList transactions = file->transactionList(filter); //if a closed account has no transactions in that timeframe, do not include it if (transactions.size() == 0) { DEBUG_OUTPUT(QString("DOES NOT INCLUDE account %1").arg(account.name())); ++it_account; continue; } } DEBUG_OUTPUT(QString("Includes account %1").arg(account.name())); // the row group is the account class (major account type) QString outergroup = MyMoneyAccount::accountTypeToString(account.accountGroup()); // extract the balance of the account for the given begin date, which is // the opening balance plus the sum of all transactions prior to the begin // date // this is in the underlying currency MyMoneyMoney value = file->balance(account.id(), from.addDays(-1)); // place into the 'opening' column... assignCell(outergroup, account, 0, value); } else { DEBUG_OUTPUT(QString("DOES NOT INCLUDE account %1").arg(account.name())); } ++it_account; } } void PivotTable::calculateRunningSums(PivotInnerGroup::iterator& it_row) { MyMoneyMoney runningsum = it_row.value()[eActual][0].calculateRunningSum(MyMoneyMoney()); int column = m_startColumn; while (column < m_numColumns) { if (it_row.value()[eActual].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateRunningSums").arg(column).arg(it_row.value()[eActual].count())); runningsum = it_row.value()[eActual][column].calculateRunningSum(runningsum); ++column; } } void PivotTable::calculateRunningSums() { DEBUG_ENTER(Q_FUNC_INFO); m_runningSumsCalculated = true; PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { #if 0 MyMoneyMoney runningsum = it_row.value()[0]; int column = m_startColumn; while (column < m_numColumns) { if (it_row.value()[eActual].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateRunningSums").arg(column).arg(it_row.value()[eActual].count())); runningsum = (it_row.value()[eActual][column] += runningsum); ++column; } #endif calculateRunningSums(it_row); ++it_row; } ++it_innergroup; } ++it_outergroup; } } MyMoneyMoney PivotTable::cellBalance(const QString& outergroup, const ReportAccount& _row, int _column, bool budget) { if (m_runningSumsCalculated) { qDebug("You must not call PivotTable::cellBalance() after calling PivotTable::calculateRunningSums()"); throw MYMONEYEXCEPTION(QString("You must not call PivotTable::cellBalance() after calling PivotTable::calculateRunningSums()")); } // for budget reports, if this is the actual value, map it to the account which // holds its budget ReportAccount row = _row; if (!budget && m_config.hasBudget()) { QString newrow = m_budgetMap[row.id()]; // if there was no mapping found, then the budget report is not interested // in this account. if (newrow.isEmpty()) return MyMoneyMoney(); - row = newrow; + row = ReportAccount(newrow); } // ensure the row already exists (and its parental hierarchy) createRow(outergroup, row, true); // Determine the inner group from the top-most parent account QString innergroup(row.topParentName()); if (m_numColumns <= _column) throw MYMONEYEXCEPTION(QString("Column %1 out of m_numColumns range (%2) in PivotTable::cellBalance").arg(_column).arg(m_numColumns)); if (m_grid[outergroup][innergroup][row][eActual].count() <= _column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::cellBalance").arg(_column).arg(m_grid[outergroup][innergroup][row][eActual].count())); MyMoneyMoney balance; if (budget) balance = m_grid[outergroup][innergroup][row][eBudget][0].cellBalance(MyMoneyMoney()); else balance = m_grid[outergroup][innergroup][row][eActual][0].cellBalance(MyMoneyMoney()); int column = m_startColumn; while (column < _column) { if (m_grid[outergroup][innergroup][row][eActual].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::cellBalance").arg(column).arg(m_grid[outergroup][innergroup][row][eActual].count())); balance = m_grid[outergroup][innergroup][row][eActual][column].cellBalance(balance); ++column; } return balance; } void PivotTable::calculateBudgetMapping() { DEBUG_ENTER(Q_FUNC_INFO); MyMoneyFile* file = MyMoneyFile::instance(); // Only do this if there is at least one budget in the file if (file->countBudgets()) { // Select a budget // // It will choose the first budget in the list for the start year of the report if no budget is selected MyMoneyBudget budget = MyMoneyBudget(); QList budgets = file->budgetList(); bool validBudget = false; //check that the selected budget is valid if (m_config.budget() != "Any") { QList::const_iterator budgets_it = budgets.constBegin(); while (budgets_it != budgets.constEnd()) { //pick the budget by id if ((*budgets_it).id() == m_config.budget()) { budget = file->budget((*budgets_it).id()); validBudget = true; break; } ++budgets_it; } } //if no valid budget has been selected if (!validBudget) { //if the budget list is empty, just return if (budgets.count() == 0) { return; } QList::const_iterator budgets_it = budgets.constBegin(); while (budgets_it != budgets.constEnd()) { //pick the first budget that matches the report start year if ((*budgets_it).budgetStart().year() == QDate::currentDate().year()) { budget = file->budget((*budgets_it).id()); break; } ++budgets_it; } //if it can't find a matching budget, take the first one on the list if (budget.id().isEmpty()) { budget = budgets[0]; } //assign the budget to the report m_config.setBudget(budget.id(), m_config.isIncludingBudgetActuals()); } // Dump the budget //qDebug() << "Budget " << budget.name() << ": "; // Go through all accounts in the system to build the mapping QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { //include only the accounts selected for the report if (m_config.includes(*it_account)) { QString id = (*it_account).id(); QString acid = id; // If the budget contains this account outright if (budget.contains(id)) { // Add it to the mapping m_budgetMap[acid] = id; // qDebug() << ReportAccount(acid).debugName() << " self-maps / type =" << budget.account(id).budgetLevel(); } // Otherwise, search for a parent account which includes sub-accounts else { //if includeBudgetActuals, include all accounts regardless of whether in budget or not if (m_config.isIncludingBudgetActuals()) { m_budgetMap[acid] = id; // qDebug() << ReportAccount(acid).debugName() << " maps to " << ReportAccount(id).debugName(); } do { id = file->account(id).parentAccountId(); if (budget.contains(id)) { if (budget.account(id).budgetSubaccounts()) { m_budgetMap[acid] = id; // qDebug() << ReportAccount(acid).debugName() << " maps to " << ReportAccount(id).debugName(); break; } } } while (! id.isEmpty()); } } ++it_account; } // end while looping through the accounts in the file // Place the budget values into the budget grid QList baccounts = budget.getaccounts(); QList::const_iterator it_bacc = baccounts.constBegin(); while (it_bacc != baccounts.constEnd()) { - ReportAccount splitAccount = (*it_bacc).id(); + ReportAccount splitAccount((*it_bacc).id()); //include the budget account only if it is included in the report if (m_config.includes(splitAccount)) { eMyMoney::Account::Type type = splitAccount.accountGroup(); QString outergroup = MyMoneyAccount::accountTypeToString(type); // reverse sign to match common notation for cash flow direction, only for expense/income splits MyMoneyMoney reverse((splitAccount.accountType() == eMyMoney::Account::Type::Expense) ? -1 : 1, 1); const QMap& periods = (*it_bacc).getPeriods(); // skip the account if it has no periods if (periods.count() < 1) { ++it_bacc; continue; } MyMoneyMoney value = (*periods.begin()).amount() * reverse; int column = m_startColumn; // based on the kind of budget it is, deal accordingly switch ((*it_bacc).budgetLevel()) { case MyMoneyBudget::AccountGroup::eYearly: // divide the single yearly value by 12 and place it in each column value /= MyMoneyMoney(12, 1); // intentional fall through case MyMoneyBudget::AccountGroup::eNone: case MyMoneyBudget::AccountGroup::eMax: case MyMoneyBudget::AccountGroup::eMonthly: // place the single monthly value in each column of the report // only add the value if columns are monthly or longer if (m_config.columnType() == MyMoneyReport::eBiMonths || m_config.columnType() == MyMoneyReport::eMonths || m_config.columnType() == MyMoneyReport::eYears || m_config.columnType() == MyMoneyReport::eQuarters) { QDate budgetDate = budget.budgetStart(); while (column < m_numColumns && budget.budgetStart().addYears(1) > budgetDate) { //only show budget values if the budget year and the column date match //no currency conversion is done here because that is done for all columns later if (budgetDate > columnDate(column)) { ++column; } else { if (budgetDate >= m_beginDate.addDays(-m_beginDate.day() + 1) && budgetDate <= m_endDate.addDays(m_endDate.daysInMonth() - m_endDate.day()) && budgetDate > (columnDate(column).addMonths(-m_config.columnType()))) { assignCell(outergroup, splitAccount, column, value, true /*budget*/); } budgetDate = budgetDate.addMonths(1); } } } break; case MyMoneyBudget::AccountGroup::eMonthByMonth: // place each value in the appropriate column // budget periods are supposed to come in order just like columns { QMap::const_iterator it_period = periods.begin(); while (it_period != periods.end() && column < m_numColumns) { if ((*it_period).startDate() > columnDate(column)) { ++column; } else { switch (m_config.columnType()) { case MyMoneyReport::eYears: case MyMoneyReport::eBiMonths: case MyMoneyReport::eQuarters: case MyMoneyReport::eMonths: { if ((*it_period).startDate() >= m_beginDate.addDays(-m_beginDate.day() + 1) && (*it_period).startDate() <= m_endDate.addDays(m_endDate.daysInMonth() - m_endDate.day()) && (*it_period).startDate() > (columnDate(column).addMonths(-m_config.columnType()))) { //no currency conversion is done here because that is done for all columns later value = (*it_period).amount() * reverse; assignCell(outergroup, splitAccount, column, value, true /*budget*/); } ++it_period; break; } default: break; } } } break; } } } ++it_bacc; } } // end if there was a budget } void PivotTable::convertToBaseCurrency() { DEBUG_ENTER(Q_FUNC_INFO); MyMoneyFile* file = MyMoneyFile::instance(); int fraction = file->baseCurrency().smallestAccountFraction(); QList rowTypeList = m_rowTypeList; rowTypeList.removeOne(eAverage); PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { auto column = 0; while (column < m_numColumns) { if (it_row.value()[eActual].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::convertToBaseCurrency").arg(column).arg(it_row.value()[eActual].count())); QDate valuedate = columnDate(column); //get base price for that date MyMoneyMoney conversionfactor = it_row.key().baseCurrencyPrice(valuedate, m_config.isSkippingZero()); int pricePrecision; if (it_row.key().isInvest()) pricePrecision = file->security(it_row.key().currencyId()).pricePrecision(); else pricePrecision = MyMoneyMoney::denomToPrec(fraction); foreach (const auto rowType, rowTypeList) { //calculate base value MyMoneyMoney oldval = it_row.value()[rowType][column]; MyMoneyMoney value = (oldval * conversionfactor).reduce(); //convert to lowest fraction if (rowType == ePrice) - it_row.value()[rowType][column] = PivotCell(value.convertPrecision(pricePrecision)); + it_row.value()[rowType][column] = PivotCell(MyMoneyMoney(value.convertPrecision(pricePrecision))); else it_row.value()[rowType][column] = PivotCell(value.convert(fraction)); DEBUG_OUTPUT_IF(conversionfactor != MyMoneyMoney::ONE , QString("Factor of %1, value was %2, now %3").arg(conversionfactor).arg(DEBUG_SENSITIVE(oldval)).arg(DEBUG_SENSITIVE(it_row.value()[rowType][column].toDouble()))); } ++column; } ++it_row; } ++it_innergroup; } ++it_outergroup; } } void PivotTable::convertToDeepCurrency() { DEBUG_ENTER(Q_FUNC_INFO); MyMoneyFile* file = MyMoneyFile::instance(); PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { auto column = 0; while (column < m_numColumns) { if (it_row.value()[eActual].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::convertToDeepCurrency").arg(column).arg(it_row.value()[eActual].count())); QDate valuedate = columnDate(column); //get conversion factor for the account and date MyMoneyMoney conversionfactor = it_row.key().deepCurrencyPrice(valuedate, m_config.isSkippingZero()); //use the fraction relevant to the account at hand int fraction = it_row.key().currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); //convert to deep currency MyMoneyMoney oldval = it_row.value()[eActual][column]; MyMoneyMoney value = (oldval * conversionfactor).reduce(); //reduce to lowest fraction it_row.value()[eActual][column] = PivotCell(value.convert(fraction)); //convert price data if (m_config.isIncludingPrice()) { MyMoneyMoney oldPriceVal = it_row.value()[ePrice][column]; MyMoneyMoney priceValue = (oldPriceVal * conversionfactor).reduce(); it_row.value()[ePrice][column] = PivotCell(priceValue.convert(10000)); } DEBUG_OUTPUT_IF(conversionfactor != MyMoneyMoney::ONE , QString("Factor of %1, value was %2, now %3").arg(conversionfactor).arg(DEBUG_SENSITIVE(oldval)).arg(DEBUG_SENSITIVE(it_row.value()[eActual][column].toDouble()))); ++column; } ++it_row; } ++it_innergroup; } ++it_outergroup; } } void PivotTable::calculateTotals() { //insert the row type that is going to be used for (int i = 0; i < m_rowTypeList.size(); ++i) { for (int k = 0; k < m_numColumns; ++k) { m_grid.m_total[ m_rowTypeList[i] ].append(PivotCell()); } } // // Outer groups // // iterate over outer groups PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { for (int k = 0; k < m_numColumns; ++k) { (*it_outergroup).m_total[ m_rowTypeList[i] ].append(PivotCell()); } } // // Inner Groups // PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { for (int k = 0; k < m_numColumns; ++k) { (*it_innergroup).m_total[ m_rowTypeList[i] ].append(PivotCell()); } } // // Rows // PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { // // Columns // auto column = 0; while (column < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) { if (it_row.value()[ m_rowTypeList[i] ].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, row columns").arg(column).arg(it_row.value()[ m_rowTypeList[i] ].count())); if ((*it_innergroup).m_total[ m_rowTypeList[i] ].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, inner group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count())); //calculate total MyMoneyMoney value = it_row.value()[ m_rowTypeList[i] ][column]; (*it_innergroup).m_total[ m_rowTypeList[i] ][column] += value; (*it_row)[ m_rowTypeList[i] ].m_total += value; } ++column; } ++it_row; } // // Inner Row Group Totals // auto column = 0; while (column < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) { if ((*it_innergroup).m_total[ m_rowTypeList[i] ].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, inner group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count())); if ((*it_outergroup).m_total[ m_rowTypeList[i] ].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, outer group totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count())); //calculate totals MyMoneyMoney value = (*it_innergroup).m_total[ m_rowTypeList[i] ][column]; (*it_outergroup).m_total[ m_rowTypeList[i] ][column] += value; (*it_innergroup).m_total[ m_rowTypeList[i] ].m_total += value; } ++column; } ++it_innergroup; } // // Outer Row Group Totals // const bool isIncomeExpense = (m_config.rowType() == MyMoneyReport::eExpenseIncome); const bool invert_total = (*it_outergroup).m_inverted; auto column = 0; while (column < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) { if (m_grid.m_total[ m_rowTypeList[i] ].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::calculateTotals, grid totals").arg(column).arg((*it_innergroup).m_total[ m_rowTypeList[i] ].count())); //calculate actual totals MyMoneyMoney value = (*it_outergroup).m_total[ m_rowTypeList[i] ][column]; (*it_outergroup).m_total[ m_rowTypeList[i] ].m_total += value; //so far the invert only applies to actual and budget if (invert_total && m_rowTypeList[i] != eBudgetDiff && m_rowTypeList[i] != eForecast) value = -value; // forecast income expense reports should be inverted as oposed to asset/liability reports if (invert_total && isIncomeExpense && m_rowTypeList[i] == eForecast) value = -value; m_grid.m_total[ m_rowTypeList[i] ][column] += value; } ++column; } ++it_outergroup; } // // Report Totals // auto totalcolumn = 0; while (totalcolumn < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) { if (m_grid.m_total[ m_rowTypeList[i] ].count() <= totalcolumn) throw MYMONEYEXCEPTION(QString("Total column %1 out of grid range (%2) in PivotTable::calculateTotals, grid totals").arg(totalcolumn).arg(m_grid.m_total[ m_rowTypeList[i] ].count())); //calculate actual totals MyMoneyMoney value = m_grid.m_total[ m_rowTypeList[i] ][totalcolumn]; m_grid.m_total[ m_rowTypeList[i] ].m_total += value; } ++totalcolumn; } } void PivotTable::assignCell(const QString& outergroup, const ReportAccount& _row, int column, MyMoneyMoney value, bool budget, bool stockSplit) { DEBUG_ENTER(Q_FUNC_INFO); DEBUG_OUTPUT(QString("Parameters: %1,%2,%3,%4,%5").arg(outergroup).arg(_row.debugName()).arg(column).arg(DEBUG_SENSITIVE(value.toDouble())).arg(budget)); // for budget reports, if this is the actual value, map it to the account which // holds its budget ReportAccount row = _row; if (!budget && m_config.hasBudget()) { QString newrow = m_budgetMap[row.id()]; // if there was no mapping found, then the budget report is not interested // in this account. if (newrow.isEmpty()) return; - row = newrow; + row = ReportAccount(newrow); } // ensure the row already exists (and its parental hierarchy) createRow(outergroup, row, true); // Determine the inner group from the top-most parent account QString innergroup(row.topParentName()); if (m_numColumns <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of m_numColumns range (%2) in PivotTable::assignCell").arg(column).arg(m_numColumns)); if (m_grid[outergroup][innergroup][row][eActual].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::assignCell").arg(column).arg(m_grid[outergroup][innergroup][row][eActual].count())); if (m_grid[outergroup][innergroup][row][eBudget].count() <= column) throw MYMONEYEXCEPTION(QString("Column %1 out of grid range (%2) in PivotTable::assignCell").arg(column).arg(m_grid[outergroup][innergroup][row][eBudget].count())); if (!stockSplit) { // Determine whether the value should be inverted before being placed in the row if (m_grid[outergroup].m_inverted) value = -value; // Add the value to the grid cell if (budget) { m_grid[outergroup][innergroup][row][eBudget][column] += value; } else { // If it is loading an actual value for a budget report // check whether it is a subaccount of a budget account (include subaccounts) // If so, check if is the same currency and convert otherwise if (m_config.hasBudget() && row.id() != _row.id() && row.currencyId() != _row.currencyId()) { ReportAccount origAcc = _row; MyMoneyMoney rate = origAcc.foreignCurrencyPrice(row.currencyId(), columnDate(column), false); m_grid[outergroup][innergroup][row][eActual][column] += (value * rate).reduce(); } else { m_grid[outergroup][innergroup][row][eActual][column] += value; } } } else { m_grid[outergroup][innergroup][row][eActual][column] += PivotCell::stockSplit(value); } } void PivotTable::createRow(const QString& outergroup, const ReportAccount& row, bool recursive) { DEBUG_ENTER(Q_FUNC_INFO); // Determine the inner group from the top-most parent account QString innergroup(row.topParentName()); if (! m_grid.contains(outergroup)) { DEBUG_OUTPUT(QString("Adding group [%1]").arg(outergroup)); m_grid[outergroup] = PivotOuterGroup(m_numColumns); } if (! m_grid[outergroup].contains(innergroup)) { DEBUG_OUTPUT(QString("Adding group [%1][%2]").arg(outergroup).arg(innergroup)); m_grid[outergroup][innergroup] = PivotInnerGroup(m_numColumns); } if (! m_grid[outergroup][innergroup].contains(row)) { DEBUG_OUTPUT(QString("Adding row [%1][%2][%3]").arg(outergroup).arg(innergroup).arg(row.debugName())); m_grid[outergroup][innergroup][row] = PivotGridRowSet(m_numColumns); if (recursive && !row.isTopLevel()) createRow(outergroup, row.parent(), recursive); } } int PivotTable::columnValue(const QDate& _date) const { if (m_config.isColumnsAreDays()) return (m_beginDate.daysTo(_date)); else return (_date.year() * 12 + _date.month()); } QDate PivotTable::columnDate(int column) const { if (m_config.isColumnsAreDays()) return m_beginDate.addDays(m_config.columnPitch() * column - m_startColumn); else return m_beginDate.addMonths(m_config.columnPitch() * column).addDays(-m_startColumn); } QString PivotTable::renderCSV() const { DEBUG_ENTER(Q_FUNC_INFO); MyMoneyFile* file = MyMoneyFile::instance(); int pricePrecision = 0; int currencyPrecision = 0; int precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); bool isMultipleCurrencies = false; // // Table Header // QString result = i18n("Account"); auto column = 0; while (column < m_numColumns) { result += QString(",%1").arg(QString(m_columnHeadings[column++])); if (m_rowTypeList.size() > 1) { QString separator; separator = separator.fill(',', m_rowTypeList.size() - 1); result += separator; } } //show total columns if (m_config.isShowingRowTotals()) result += QString(",%1").arg(i18nc("Total balance", "Total")); result += '\n'; // Row Type Header if (m_rowTypeList.size() > 1) { auto column = 0; while (column < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString(",%1").arg(m_columnTypeHeaderList[i]); } column++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString(",%1").arg(m_columnTypeHeaderList[i]); } } result += '\n'; } // // Outer groups // // iterate over outer groups PivotGrid::const_iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { // // Outer Group Header // if (!(m_config.isIncludingPrice() || m_config.isIncludingAveragePrice())) result += it_outergroup.key() + '\n'; // // Inner Groups // PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin(); int rownum = 0; while (it_innergroup != (*it_outergroup).end()) { // // Rows // QString innergroupdata; PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { ReportAccount rowname = it_row.key(); // // Columns // QString rowdata; auto column = 0; bool isUsed = false; for (int i = 0; i < m_rowTypeList.size(); ++i) isUsed |= it_row.value()[ m_rowTypeList[i] ][0].isUsed(); if (it_row.key().accountType() != eMyMoney::Account::Type::Investment) { while (column < m_numColumns) { //show columns foreach (const auto rowType, m_rowTypeList) { if (rowType == ePrice) { if (pricePrecision == 0) { if (it_row.key().isInvest()) { pricePrecision = file->currency(it_row.key().currencyId()).pricePrecision(); precision = pricePrecision; } else precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); } else precision = pricePrecision; } else { if (currencyPrecision == 0) { if (it_row.key().isInvest()) // stock account isn't eveluated in currency, so take investment account instead currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().parent().fraction()); else currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().fraction()); precision = currencyPrecision; } else precision = currencyPrecision; } rowdata += QString(",\"%1\"").arg(it_row.value()[rowType][column].formatMoney(QString(), precision, false)); isUsed |= it_row.value()[rowType][column].isUsed(); } column++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) rowdata += QString(",\"%1\"").arg((*it_row)[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false)); } } else { for (auto i = 0; i < m_numColumns + m_rowTypeList.size(); ++i) rowdata.append(',');; } // // Row Header // if (!rowname.isClosed() || isUsed) { innergroupdata += "\"" + QString().fill(' ', rowname.hierarchyDepth() - 1) + rowname.name(); // if we don't convert the currencies to the base currency and the // current row contains a foreign currency, then we append the currency // to the name of the account if (!m_config.isConvertCurrency() && rowname.isForeignCurrency()) innergroupdata += QString(" (%1)").arg(rowname.currencyId()); innergroupdata += '\"'; if (isUsed) innergroupdata += rowdata; innergroupdata += '\n'; if (!isMultipleCurrencies && rowname.isForeignCurrency()) isMultipleCurrencies = true; if (!m_containsNonBaseCurrency && rowname.isForeignCurrency()) m_containsNonBaseCurrency = true; } ++it_row; } // // Inner Row Group Totals // bool finishrow = true; QString finalRow; bool isUsed = false; if (m_config.detailLevel() == MyMoneyReport::eDetailAll && ((*it_innergroup).size() > 1)) { // Print the individual rows result += innergroupdata; if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) { // Start the TOTALS row finalRow = i18nc("Total balance", "Total"); isUsed = true; } else { ++rownum; finishrow = false; } } else { // Start the single INDIVIDUAL ACCOUNT row ReportAccount rowname = (*it_innergroup).begin().key(); isUsed |= !rowname.isClosed(); finalRow = "\"" + QString().fill(' ', rowname.hierarchyDepth() - 1) + rowname.name(); if (!m_config.isConvertCurrency() && rowname.isForeignCurrency()) finalRow += QString(" (%1)").arg(rowname.currencyId()); finalRow += "\""; } // Finish the row started above, unless told not to if (finishrow) { auto column = 0; for (int i = 0; i < m_rowTypeList.size(); ++i) isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][0].isUsed(); while (column < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) { isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][column].isUsed(); finalRow += QString(",\"%1\"").arg((*it_innergroup).m_total[ m_rowTypeList[i] ][column].formatMoney(QString(), precision, false)); } column++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) finalRow += QString(",\"%1\"").arg((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false)); } finalRow += '\n'; } if (isUsed) { result += finalRow; ++rownum; } ++it_innergroup; } // // Outer Row Group Totals // if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) { result += QString("%1 %2").arg(i18nc("Total balance", "Total")).arg(it_outergroup.key()); auto column = 0; while (column < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) result += QString(",\"%1\"").arg((*it_outergroup).m_total[ m_rowTypeList[i] ][column].formatMoney(QString(), precision, false)); column++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) result += QString(",\"%1\"").arg((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false)); } result += '\n'; } ++it_outergroup; } // // Report Totals // if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) { result += i18n("Grand Total"); auto totalcolumn = 0; while (totalcolumn < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) result += QString(",\"%1\"").arg(m_grid.m_total[ m_rowTypeList[i] ][totalcolumn].formatMoney(QString(), precision, false)); totalcolumn++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) result += QString(",\"%1\"").arg(m_grid.m_total[ m_rowTypeList[i] ].m_total.formatMoney(QString(), precision, false)); } result += '\n'; } return result; } QString PivotTable::renderHTML() const { DEBUG_ENTER(Q_FUNC_INFO); MyMoneyFile* file = MyMoneyFile::instance(); int pricePrecision = 0; int currencyPrecision = 0; int precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); QString colspan = QString(" colspan=\"%1\"").arg(m_numColumns + 1 + (m_config.isShowingRowTotals() ? 1 : 0)); // setup a leftborder for better readability of budget vs actual reports QString leftborder; if (m_rowTypeList.size() > 1) leftborder = " class=\"leftborder\""; // // Table Header // QString result = QString("\n\n\n" "\n").arg(i18n("Account")); QString headerspan; int span = m_rowTypeList.size(); headerspan = QString(" colspan=\"%1\"").arg(span); auto column = 0; while (column < m_numColumns) result += QString("%2").arg(headerspan, QString(m_columnHeadings[column++]).replace(QRegExp(" "), "
")); if (m_config.isShowingRowTotals()) result += QString("%2").arg(headerspan).arg(i18nc("Total balance", "Total")); result += "
\n"; // // Header for multiple columns // if (span > 1) { result += ""; auto column = 0; while (column < m_numColumns) { QString lb; if (column != 0) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(m_columnTypeHeaderList[i]) .arg(i == 0 ? lb : QString()); } column++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(m_columnTypeHeaderList[i]) .arg(i == 0 ? leftborder : QString()); } } result += ""; } // Skip the body of the report if the report only calls for totals to be shown if (m_config.detailLevel() != MyMoneyReport::eDetailTotal) { // // Outer groups // // Need to sort the outergroups. They can't always be sorted by name. So we create a list of // map iterators, and sort that. Then we'll iterate through the map iterators and use those as // before. // // I hope this doesn't bog the performance of reports, given that we're copying the entire report // data. If this is a perf hit, we could change to storing outergroup pointers, I think. QList outergroups; PivotGrid::const_iterator it_outergroup_map = m_grid.begin(); while (it_outergroup_map != m_grid.end()) { outergroups.push_back(it_outergroup_map.value()); // copy the name into the outergroup, because we will now lose any association with // the map iterator outergroups.back().m_displayName = it_outergroup_map.key(); ++it_outergroup_map; } qSort(outergroups.begin(), outergroups.end()); QList::const_iterator it_outergroup = outergroups.constBegin(); while (it_outergroup != outergroups.constEnd()) { // // Outer Group Header // if (!(m_config.isIncludingPrice() || m_config.isIncludingAveragePrice())) result += QString("\n").arg(colspan).arg((*it_outergroup).m_displayName); // Skip the inner groups if the report only calls for outer group totals to be shown if (m_config.detailLevel() != MyMoneyReport::eDetailGroup) { // // Inner Groups // PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin(); int rownum = 0; while (it_innergroup != (*it_outergroup).end()) { // // Rows // QString innergroupdata; PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { // // Columns // QString rowdata; auto column = 0; pricePrecision = 0; // new row => new account => new precision currencyPrecision = 0; bool isUsed = it_row.value()[eActual][0].isUsed(); if (it_row.key().accountType() != eMyMoney::Account::Type::Investment) { while (column < m_numColumns) { QString lb; if (column > 0) lb = leftborder; foreach (const auto rowType, m_rowTypeList) { if (rowType == ePrice) { if (pricePrecision == 0) { if (it_row.key().isInvest()) { pricePrecision = file->currency(it_row.key().currencyId()).pricePrecision(); precision = pricePrecision; } else precision = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); } else precision = pricePrecision; } else { if (currencyPrecision == 0) { if (it_row.key().isInvest()) // stock account isn't eveluated in currency, so take investment account instead currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().parent().fraction()); else currencyPrecision = MyMoneyMoney::denomToPrec(it_row.key().fraction()); precision = currencyPrecision; } else precision = currencyPrecision; } rowdata += QString("%1") .arg(coloredAmount(it_row.value()[rowType][column], QString(), precision)) .arg(lb); lb.clear(); isUsed |= it_row.value()[rowType][column].isUsed(); } ++column; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { rowdata += QString("%1") .arg(coloredAmount(it_row.value()[ m_rowTypeList[i] ].m_total, QString(), precision)) .arg(i == 0 ? leftborder : QString()); } } } else rowdata += QString(QLatin1Literal("")).arg(m_numColumns + m_rowTypeList.size()); // // Row Header // ReportAccount rowname = it_row.key(); // don't show closed accounts if they have not been used if (!rowname.isClosed() || isUsed) { innergroupdata += QString("%5%6") .arg(rownum & 0x01 ? "even" : "odd") .arg(rowname.isTopLevel() ? " id=\"topparent\"" : "") .arg("") //.arg((*it_row).m_total.isZero() ? colspan : "") // colspan the distance if this row will be blank .arg(rowname.hierarchyDepth() - 1) .arg(rowname.name().replace(QRegExp(" "), " ")) .arg((m_config.isConvertCurrency() || !rowname.isForeignCurrency()) ? QString() : QString(" (%1)").arg(rowname.currency().id())); // Don't print this row if it's going to be all zeros // TODO: Uncomment this, and deal with the case where the data // is zero, but the budget is non-zero //if ( !(*it_row).m_total.isZero() ) innergroupdata += rowdata; innergroupdata += "\n"; if (!m_containsNonBaseCurrency && rowname.isForeignCurrency()) m_containsNonBaseCurrency = true; } ++it_row; } // // Inner Row Group Totals // bool finishrow = true; QString finalRow; bool isUsed = false; if (m_config.detailLevel() == MyMoneyReport::eDetailAll && ((*it_innergroup).size() > 1)) { // Print the individual rows result += innergroupdata; if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) { // Start the TOTALS row finalRow = QString("") .arg(rownum & 0x01 ? "even" : "odd") .arg(i18nc("Total balance", "Total")); // don't suppress display of totals isUsed = true; } else { finishrow = false; ++rownum; } } else { // Start the single INDIVIDUAL ACCOUNT row // FIXME: There is a bit of a bug here with class=leftX. There's only a finite number // of classes I can define in the .CSS file, and the user can theoretically nest deeper. // The right solution is to use style=Xem, and calculate X. Let's see if anyone complains // first :) Also applies to the row header case above. // FIXED: I found it in one of my reports and changed it to the proposed method. // This works for me (ipwizard) ReportAccount rowname = (*it_innergroup).begin().key(); isUsed |= !rowname.isClosed(); finalRow = QString("") .arg(rownum & 0x01 ? "even" : "odd") .arg(m_config.detailLevel() == MyMoneyReport::eDetailAll ? "id=\"solo\"" : "") .arg(rowname.hierarchyDepth() - 1) .arg(rowname.name().replace(QRegExp(" "), " ")) .arg((m_config.isConvertCurrency() || !rowname.isForeignCurrency()) ? QString() : QString(" (%1)").arg(rowname.currency().id())); } // Finish the row started above, unless told not to if (finishrow) { auto column = 0; isUsed |= (*it_innergroup).m_total[eActual][0].isUsed(); while (column < m_numColumns) { QString lb; if (column != 0) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { finalRow += QString("%1") .arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ][column], QString(), precision)) .arg(i == 0 ? lb : QString()); isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][column].isUsed(); } column++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { finalRow += QString("%1") .arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total, QString(), precision)) .arg(i == 0 ? leftborder : QString()); } } finalRow += "\n"; if (isUsed) { result += finalRow; ++rownum; } } ++it_innergroup; } // end while iterating on the inner groups } // end if detail level is not "group" // // Outer Row Group Totals // if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) { result += QString("").arg(i18nc("Total balance", "Total")).arg((*it_outergroup).m_displayName); auto column = 0; while (column < m_numColumns) { QString lb; if (column != 0) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ][column], QString(), precision)) .arg(i == 0 ? lb : QString()); } column++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total, QString(), precision)) .arg(i == 0 ? leftborder : QString()); } } result += "\n"; } ++it_outergroup; } // end while iterating on the outergroups } // end if detail level is not "total" // // Report Totals // if (m_config.isConvertCurrency() && m_config.isShowingColumnTotals()) { result += QString("\n"); result += QString("").arg(i18n("Grand Total")); auto totalcolumn = 0; while (totalcolumn < m_numColumns) { QString lb; if (totalcolumn != 0) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ][totalcolumn], QString(), precision)) .arg(i == 0 ? lb : QString()); } totalcolumn++; } if (m_config.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ].m_total, QString(), precision)) .arg(i == 0 ? leftborder : QString()); } } result += "\n"; } result += "
%1
%2
  %2
%5%6
%1 %2
 
%1
\n"; return result; } void PivotTable::dump(const QString& file, const QString& /* context */) const { QFile g(file); g.open(QIODevice::WriteOnly); QTextStream(&g) << renderHTML(); g.close(); } void PivotTable::drawChart(KReportChartView& chartView) const { chartView.drawPivotChart(m_grid, m_config, m_numColumns, m_columnHeadings, m_rowTypeList, m_columnTypeHeaderList); } QString PivotTable::coloredAmount(const MyMoneyMoney& amount, const QString& currencySymbol, int prec) const { const auto value = amount.formatMoney(currencySymbol, prec); if (amount.isNegative()) return QString::fromLatin1("%2") .arg(KMyMoneyGlobalSettings::schemeColor(SchemeColor::Negative).name(), value); else return value; } void PivotTable::calculateBudgetDiff() { PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { int column = m_startColumn; switch (it_row.key().accountGroup()) { case eMyMoney::Account::Type::Income: case eMyMoney::Account::Type::Asset: while (column < m_numColumns) { - it_row.value()[eBudgetDiff][column] = it_row.value()[eActual][column] - it_row.value()[eBudget][column]; + it_row.value()[eBudgetDiff][column] = PivotCell(it_row.value()[eActual][column] - it_row.value()[eBudget][column]); ++column; } break; case eMyMoney::Account::Type::Expense: case eMyMoney::Account::Type::Liability: while (column < m_numColumns) { - it_row.value()[eBudgetDiff][column] = it_row.value()[eBudget][column] - it_row.value()[eActual][column]; + it_row.value()[eBudgetDiff][column] = PivotCell(it_row.value()[eBudget][column] - it_row.value()[eActual][column]); ++column; } break; default: break; } ++it_row; } ++it_innergroup; } ++it_outergroup; } } void PivotTable::calculateForecast() { //setup forecast MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast(); //since this is a net worth forecast we want to include all account even those that are not in use forecast.setIncludeUnusedAccounts(true); //setup forecast dates if (m_endDate > QDate::currentDate()) { forecast.setForecastEndDate(m_endDate); forecast.setForecastStartDate(QDate::currentDate()); forecast.setForecastDays(QDate::currentDate().daysTo(m_endDate)); } else { forecast.setForecastStartDate(m_beginDate); forecast.setForecastEndDate(m_endDate); forecast.setForecastDays(m_beginDate.daysTo(m_endDate) + 1); } //adjust history dates if beginning date is before today if (m_beginDate < QDate::currentDate()) { forecast.setHistoryEndDate(m_beginDate.addDays(-1)); forecast.setHistoryStartDate(forecast.historyEndDate().addDays(-forecast.accountsCycle()*forecast.forecastCycles())); } //run forecast if (m_config.rowType() == MyMoneyReport::eAssetLiability) { //asset and liability forecast.doForecast(); } else { //income and expenses MyMoneyBudget budget; forecast.createBudget(budget, m_beginDate.addYears(-1), m_beginDate.addDays(-1), m_beginDate, m_endDate, false); } //go through the data and add forecast PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { int column = m_startColumn; QDate forecastDate = m_beginDate; //check whether columns are days or months if (m_config.isColumnsAreDays()) { while (column < m_numColumns) { - it_row.value()[eForecast][column] = forecast.forecastBalance(it_row.key(), forecastDate); + it_row.value()[eForecast][column] = PivotCell(forecast.forecastBalance(it_row.key(), forecastDate)); forecastDate = forecastDate.addDays(1); ++column; } } else { //if columns are months while (column < m_numColumns) { // the forecast balance is on the first day of the month see MyMoneyForecast::calculateScheduledMonthlyBalances() forecastDate = QDate(forecastDate.year(), forecastDate.month(), 1); //check that forecastDate is not over ending date if (forecastDate > m_endDate) forecastDate = m_endDate; //get forecast balance and set the corresponding column - it_row.value()[eForecast][column] = forecast.forecastBalance(it_row.key(), forecastDate); + it_row.value()[eForecast][column] = PivotCell(forecast.forecastBalance(it_row.key(), forecastDate)); forecastDate = forecastDate.addMonths(1); ++column; } } ++it_row; } ++it_innergroup; } ++it_outergroup; } } void PivotTable::loadRowTypeList() { if ((m_config.isIncludingBudgetActuals()) || (!m_config.hasBudget() && !m_config.isIncludingForecast() && !m_config.isIncludingMovingAverage() && !m_config.isIncludingPrice() && !m_config.isIncludingAveragePrice()) ) { m_rowTypeList.append(eActual); m_columnTypeHeaderList.append(i18n("Actual")); } if (m_config.hasBudget()) { m_rowTypeList.append(eBudget); m_columnTypeHeaderList.append(i18n("Budget")); } if (m_config.isIncludingBudgetActuals()) { m_rowTypeList.append(eBudgetDiff); m_columnTypeHeaderList.append(i18n("Difference")); } if (m_config.isIncludingForecast()) { m_rowTypeList.append(eForecast); m_columnTypeHeaderList.append(i18n("Forecast")); } if (m_config.isIncludingMovingAverage()) { m_rowTypeList.append(eAverage); m_columnTypeHeaderList.append(i18n("Moving Average")); } if (m_config.isIncludingAveragePrice()) { m_rowTypeList.append(eAverage); m_columnTypeHeaderList.append(i18n("Moving Average Price")); } if (m_config.isIncludingPrice()) { m_rowTypeList.append(ePrice); m_columnTypeHeaderList.append(i18n("Price")); } } void PivotTable::calculateMovingAverage() { int delta = m_config.movingAverageDays() / 2; //go through the data and add the moving average PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { int column = m_startColumn; //check whether columns are days or months if (m_config.columnType() == MyMoneyReport::eDays) { while (column < m_numColumns) { MyMoneyMoney totalPrice = MyMoneyMoney(); QDate averageStart = columnDate(column).addDays(-delta); QDate averageEnd = columnDate(column).addDays(delta); for (QDate averageDate = averageStart; averageDate <= averageEnd; averageDate = averageDate.addDays(1)) { if (m_config.isConvertCurrency()) { totalPrice += it_row.key().deepCurrencyPrice(averageDate) * it_row.key().baseCurrencyPrice(averageDate); } else { totalPrice += it_row.key().deepCurrencyPrice(averageDate); } totalPrice = totalPrice.convert(10000); } //calculate the average price MyMoneyMoney averagePrice = totalPrice / MyMoneyMoney((averageStart.daysTo(averageEnd) + 1), 1); //get the actual value, multiply by the average price and save that value MyMoneyMoney averageValue = it_row.value()[eActual][column] * averagePrice; - it_row.value()[eAverage][column] = averageValue.convert(10000); + it_row.value()[eAverage][column] = PivotCell(averageValue.convert(10000)); ++column; } } else { //if columns are months while (column < m_numColumns) { QDate averageStart = columnDate(column); //set the right start date depending on the column type switch (m_config.columnType()) { case MyMoneyReport::eYears: { averageStart = QDate(columnDate(column).year(), 1, 1); break; } case MyMoneyReport::eBiMonths: { averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1); break; } case MyMoneyReport::eQuarters: { averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1); break; } case MyMoneyReport::eMonths: { averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1); break; } case MyMoneyReport::eWeeks: { averageStart = columnDate(column).addDays(-columnDate(column).dayOfWeek() + 1); break; } default: break; } //gather the actual data and calculate the average MyMoneyMoney totalPrice = MyMoneyMoney(); QDate averageEnd = columnDate(column); for (QDate averageDate = averageStart; averageDate <= averageEnd; averageDate = averageDate.addDays(1)) { if (m_config.isConvertCurrency()) { totalPrice += it_row.key().deepCurrencyPrice(averageDate) * it_row.key().baseCurrencyPrice(averageDate); } else { totalPrice += it_row.key().deepCurrencyPrice(averageDate); } totalPrice = totalPrice.convert(10000); } MyMoneyMoney averagePrice = totalPrice / MyMoneyMoney((averageStart.daysTo(averageEnd) + 1), 1); MyMoneyMoney averageValue = it_row.value()[eActual][column] * averagePrice; //fill in the average - it_row.value()[eAverage][column] = averageValue.convert(10000); + it_row.value()[eAverage][column] = PivotCell(averageValue.convert(10000)); ++column; } } ++it_row; } ++it_innergroup; } ++it_outergroup; } } void PivotTable::fillBasePriceUnit(ERowType rowType) { MyMoneyFile* file = MyMoneyFile::instance(); QString baseCurrencyId = file->baseCurrency().id(); //get the first price date for securities QMap securityDates = securityFirstPrice(); //go through the data PivotGrid::iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { int column = m_startColumn; //if it is a base currency fill all the values bool firstPriceExists = false; if (it_row.key().currencyId() == baseCurrencyId) { firstPriceExists = true; } while (column < m_numColumns) { //check whether the date for that column is on or after the first price if (!firstPriceExists && securityDates.contains(it_row.key().currencyId()) && columnDate(column) >= securityDates.value(it_row.key().currencyId())) { firstPriceExists = true; } //only add the dummy value if there is a price for that date if (firstPriceExists) { //insert a unit of currency for each account - it_row.value()[rowType][column] = MyMoneyMoney::ONE; + it_row.value()[rowType][column] = PivotCell(MyMoneyMoney::ONE); } ++column; } ++it_row; } ++it_innergroup; } ++it_outergroup; } } QMap PivotTable::securityFirstPrice() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyPriceList priceList = file->priceList(); QMap securityPriceDate; MyMoneyPriceList::const_iterator prices_it; for (prices_it = priceList.constBegin(); prices_it != priceList.constEnd(); ++prices_it) { MyMoneyPrice firstPrice = (*((*prices_it).constBegin())); //check the security in the from field //if it is there, check if it is older if (securityPriceDate.contains(firstPrice.from())) { if (securityPriceDate.value(firstPrice.from()) > firstPrice.date()) { securityPriceDate[firstPrice.from()] = firstPrice.date(); } } else { securityPriceDate.insert(firstPrice.from(), firstPrice.date()); } //check the security in the to field //if it is there, check if it is older if (securityPriceDate.contains(firstPrice.to())) { if (securityPriceDate.value(firstPrice.to()) > firstPrice.date()) { securityPriceDate[firstPrice.to()] = firstPrice.date(); } } else { securityPriceDate.insert(firstPrice.to(), firstPrice.date()); } } return securityPriceDate; } void PivotTable::includeInvestmentSubAccounts() { // if we're not in expert mode, we need to make sure // that all stock accounts for the selected investment // account are also selected QStringList accountList; if (m_config.accounts(accountList)) { if (!KMyMoneyGlobalSettings::expertMode()) { QStringList::const_iterator it_a, it_b; for (it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == eMyMoney::Account::Type::Investment) { for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) { if (!accountList.contains(*it_b)) { m_config.addAccount(*it_b); } } } } } } } int PivotTable::currentDateColumn() { //return -1 if the columns do not include the current date if (m_beginDate > QDate::currentDate() || m_endDate < QDate::currentDate()) { return -1; } //check the date of each column and return if it is the one for the current date //if columns are not days, return the one for the current month or year int column = m_startColumn; while (column < m_numColumns) { if (columnDate(column) >= QDate::currentDate()) { break; } column++; } //if there is no column matching the current date, return -1 if (column == m_numColumns) { column = -1; } return column; } } // namespace diff --git a/kmymoney/reports/pivottable.h b/kmymoney/reports/pivottable.h index a8974da5f..595da3738 100644 --- a/kmymoney/reports/pivottable.h +++ b/kmymoney/reports/pivottable.h @@ -1,372 +1,372 @@ /*************************************************************************** pivottable.h ------------------- begin : Sat May 22 2004 copyright : (C) 2004-2005 by Ace Jones Thomas Baumgart Alvaro Soliverez ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef PIVOTTABLE_H #define PIVOTTABLE_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "reporttable.h" #include "pivotgrid.h" #include "reportaccount.h" class MyMoneyReport; namespace reports { class KReportChartView; } namespace reports { /** * Calculates a 'pivot table' of information about the transaction database. * Based on pivot tables in MS Excel, and implemented as 'Data Pilot' in * OpenOffice.Org Calc. * * | Month,etc * -------------+------------ * Expense Type | Sum(Value) * Category | * * This is a middle-layer class, between the UI and the engine. The * MyMoneyReport class holds only the CONFIGURATION parameters. This * class actually does the work of retrieving the data from the engine * and formatting it for the user. * * @author Ace Jones * * @short **/ class PivotTable : public ReportTable { KMM_MYMONEY_UNIT_TESTABLE public: /** * Create a Pivot table style report * * @param _report The configuration parameters for this report */ - PivotTable(const MyMoneyReport& _report); + explicit PivotTable(const MyMoneyReport& _report); /** * virtual Destructur */ virtual ~PivotTable() {} /** * Render the report body to an HTML stream. * * @return QString HTML string representing the report body */ QString renderHTML() const; /** * Render the report to a comma-separated-values stream. * * @return QString CSV string representing the report */ QString renderCSV() const; /** * Render the report to a graphical chart * * @param view The KReportChartView into which to draw the chart. */ void drawChart(KReportChartView& view) const; /** * Dump the report's HTML to a file * * @param file The filename to dump into * @param context unused, but provided for interface compatibility */ void dump(const QString& file, const QString& context = QString()) const; /** * Returns the grid generated by the report * */ PivotGrid grid() { return m_grid; } protected: void init(); // used for debugging the constructor private: PivotGrid m_grid; QStringList m_columnHeadings; int m_numColumns; QDate m_beginDate; QDate m_endDate; bool m_runningSumsCalculated; int m_startColumn; /** * For budget-vs-actual reports only, maps each account to the account which holds * the budget for it. If an account is not contained in this map, it is not included * in the budget. */ QMap m_budgetMap; /** * This list contains the types of PivotGridRows that are going to be shown in the report */ QList m_rowTypeList; /** * This list contains the i18n headers for the column types */ QStringList m_columnTypeHeaderList; /** * This method returns the formatted value of @a amount with * a possible @a currencySymbol added and @a prec fractional digits. * @a currencySymbol defaults to be empty and @a prec defaults to 2. * * If @a amount is negative the formatted value is enclosed in an * HTML font tag to modify the color to reflect the user settings for * negtive numbers. * * Example: 1.23 is returned as '1.23' whereas -1.23 is returned as * @verbatim -1.23@endverbatim * with $red, $green and $blue being the actual value for the * chosen color. */ QString coloredAmount(const MyMoneyMoney& amount, const QString& currencySymbol, int prec) const; protected: /** * Creates a row in the grid if it doesn't already exist * * Downsteam assignment functions will assume that this row already * exists, so this function creates a row of the needed length populated * with zeros. * * @param outergroup The outer row group * @param row The row itself * @param recursive Whether to also recursively create rows for our parent accounts */ void createRow(const QString& outergroup, const ReportAccount& row, bool recursive); /** * Assigns a value into the grid * * Adds the given value to the value which already exists at the specified grid position * * @param outergroup The outer row group * @param row The row itself * @param column The column * @param value The value to be added in * @param budget Whether this is a budget value (@p true) or an actual * value (@p false). Defaults to @p false. * @param stockSplit Whether this is a stock split (@p true) or an actual * value (@p false). Defaults to @p false. */ inline void assignCell(const QString& outergroup, const ReportAccount& row, int column, MyMoneyMoney value, bool budget = false, bool stockSplit = false); /** * Create a row for each included account. This is used when * the config parameter isIncludingUnusedAccount() is true */ void createAccountRows(); /** * Record the opening balances of all qualifying accounts into the grid. * * For accounts opened before the report period, places the balance into the '0' column. * For those opened during the report period, places the balance into the appropriate column * for the month when it was opened. */ void calculateOpeningBalances(); /** * Calculate budget mapping * * For budget-vs-actual reports, this creates a mapping between each account * in the user's hierarchy and the account where the budget is held for it. * This is needed because the user can budget on a given account for that * account and all its descendants. Also if NO budget is placed on the * account or any of its parents, the account is not included in the map. */ void calculateBudgetMapping(); /** * Calculate the running sums. * * After calling this method, each cell of the report will contain the running sum of all * the cells in its row in this and earlier columns. * * For example, consider a row with these values: * 01 02 03 04 05 06 07 08 09 10 * * After calling this function, the row will look like this: * 01 03 06 10 15 21 28 36 45 55 */ void calculateRunningSums(); void calculateRunningSums(PivotInnerGroup::iterator& it_row); /** * This method calculates the difference between a @a budgeted and an @a * actual amount. The calculation is based on the type of the * @a repAccount. The difference value is calculated as follows: * * If @a repAccount is of type eMyMoney::Account::Type::Income * * @code * diff = actual - budgeted * @endcode * * If @a repAccount is of type eMyMoney::Account::Type::Expense * * @code * diff = budgeted - actual * @endcode * * In all other cases, 0 is returned. */ void calculateBudgetDiff(); /** * This method calculates forecast for a report */ void calculateForecast(); /** * This method inserts units to be used to display prices */ void fillBasePriceUnit(ERowType rowType); /** * This method collects the first date for which there is a price for every security */ QMap securityFirstPrice(); /** * This method calculates moving average for a report */ void calculateMovingAverage(); /** * Calculate the row and column totals * * This function will set the m_total members of all the TGrid objects. Be sure the values are * all converted to the base currency first!! * */ void calculateTotals(); /** * Convert each value in the grid to the base currency * */ void convertToBaseCurrency(); /** * Convert each value in the grid to the account/category's deep currency * * See AccountDescriptor::deepCurrencyPrice() for a description of 'deep' currency * */ void convertToDeepCurrency(); /** * Turn month-long columns into larger time periods if needed * * For example, consider a row with these values: * 01 02 03 04 05 06 07 08 09 10 * * If the column pitch is 3 (i.e. quarterly), after calling this function, * the row will look like this: * 06 15 26 10 */ void collapseColumns(); /** * Determine the proper column headings based on the time periods covered by each column * */ void calculateColumnHeadings(); /** * Helper methods for collapseColumns * */ void accumulateColumn(int destcolumn, int sourcecolumn); void clearColumn(int column); /** * Calculate the column of a given date. This is the absolute column in a * hypothetical report that covers all of known time. In reality an actual * report will be a subset of that. * * @param _date The date */ int columnValue(const QDate& _date) const; /** * Calculate the date of the last day covered by a given column. * * @param column The column */ QDate columnDate(int column) const; /** * Returns the balance of a given cell. Throws an exception once calculateRunningSums() has been run. */ MyMoneyMoney cellBalance(const QString& outergroup, const ReportAccount& _row, int column, bool budget); /** * Draws a PivotGridRowSet in a chart for the given ERowType */ unsigned drawChartRowSet(int rowNum, const bool seriesTotals, const bool accountSeries, KReportChartView& chartView, const PivotGridRowSet& rowSet, const ERowType rowType) const; /** * Loads m_rowTypeList with the list of PivotGridRow types that the reporttable * should show */ void loadRowTypeList(); /** * If not in expert mode, include all subaccounts for each selected * investment account */ void includeInvestmentSubAccounts(); /** * Returns the column which holds the current date * Returns -1 if the current date is not within range */ int currentDateColumn(); }; } #endif // PIVOTTABLE_H diff --git a/kmymoney/reports/querytable.cpp b/kmymoney/reports/querytable.cpp index 4b99eaa22..37453f7f8 100644 --- a/kmymoney/reports/querytable.cpp +++ b/kmymoney/reports/querytable.cpp @@ -1,2168 +1,2168 @@ /*************************************************************************** querytable.cpp ------------------- begin : Fri Jul 23 2004 copyright : (C) 2004-2005 by Ace Jones (C) 2007 Sascha Pfau (C) 2017 Łukasz Wojniłowicz ***************************************************************************/ /**************************************************************************** Contains code from the func_xirr and related methods of financial.cpp - KOffice 1.6 by Sascha Pfau. Sascha agreed to relicense those methods under GPLv2 or later. *****************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "querytable.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyinstitution.h" #include "mymoneyprice.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyreport.h" #include "mymoneyexception.h" #include "kmymoneyutils.h" #include "reportaccount.h" #include "mymoneyenums.h" namespace reports { // **************************************************************************** // // CashFlowListItem implementation // // Cash flow analysis tools for investment reports // // **************************************************************************** QDate CashFlowListItem::m_sToday = QDate::currentDate(); MyMoneyMoney CashFlowListItem::NPV(double _rate) const { double T = static_cast(m_sToday.daysTo(m_date)) / 365.0; MyMoneyMoney result(m_value.toDouble() / pow(1 + _rate, T), 100); //qDebug() << "CashFlowListItem::NPV( " << _rate << " ) == " << result; return result; } // **************************************************************************** // // CashFlowList implementation // // Cash flow analysis tools for investment reports // // **************************************************************************** CashFlowListItem CashFlowList::mostRecent() const { CashFlowList dupe(*this); qSort(dupe); //qDebug() << " CashFlowList::mostRecent() == " << dupe.back().date().toString(Qt::ISODate); return dupe.back(); } MyMoneyMoney CashFlowList::NPV(double _rate) const { MyMoneyMoney result; const_iterator it_cash = constBegin(); while (it_cash != constEnd()) { result += (*it_cash).NPV(_rate); ++it_cash; } //qDebug() << "CashFlowList::NPV( " << _rate << " ) == " << result << "------------------------" << endl; return result; } double CashFlowList::calculateXIRR() const { double resultRate = 0.00001; double resultZero = 0.00000; //if ( args.count() > 2 ) // resultRate = calc->conv()->asFloat ( args[2] ).asFloat(); // check pairs and count >= 2 and guess > -1.0 //if ( args[0].count() != args[1].count() || args[1].count() < 2 || resultRate <= -1.0 ) // return Value::errorVALUE(); // define max epsilon static const double maxEpsilon = 1e-5; // max number of iterations static const int maxIter = 50; // Newton's method - try to find a res, with a accuracy of maxEpsilon double rateEpsilon, newRate, resultValue; int i = 0; bool contLoop; do { resultValue = xirrResult(resultRate); double resultDerive = xirrResultDerive(resultRate); //check what happens if xirrResultDerive is zero //Don't know if it is correct to dismiss the result if (resultDerive != 0) { newRate = resultRate - resultValue / resultDerive; } else { newRate = resultRate - resultValue; } rateEpsilon = fabs(newRate - resultRate); resultRate = newRate; contLoop = (rateEpsilon > maxEpsilon) && (fabs(resultValue) > maxEpsilon); } while (contLoop && (++i < maxIter)); if (contLoop) return resultZero; return resultRate; } double CashFlowList::xirrResult(double& rate) const { QDate date; double r = rate + 1.0; double res = 0.00000;//back().value().toDouble(); QList::const_iterator list_it = constBegin(); while (list_it != constEnd()) { double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0; MyMoneyMoney val = (* list_it).value(); if (e_i < 0) { res += val.toDouble() * pow(r, -e_i); } else { res += val.toDouble() / pow(r, e_i); } ++list_it; } return res; } double CashFlowList::xirrResultDerive(double& rate) const { QDate date; double r = rate + 1.0; double res = 0.00000; QList::const_iterator list_it = constBegin(); while (list_it != constEnd()) { double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0; MyMoneyMoney val = (* list_it).value(); res -= e_i * val.toDouble() / pow(r, e_i + 1.0); ++list_it; } return res; } double CashFlowList::IRR() const { double result = 0.0; // set 'today', which is the most recent of all dates in the list CashFlowListItem::setToday(mostRecent().date()); result = calculateXIRR(); return result; } MyMoneyMoney CashFlowList::total() const { MyMoneyMoney result; const_iterator it_cash = constBegin(); while (it_cash != constEnd()) { result += (*it_cash).value(); ++it_cash; } return result; } void CashFlowList::dumpDebug() const { const_iterator it_item = constBegin(); while (it_item != constEnd()) { qDebug() << (*it_item).date().toString(Qt::ISODate) << " " << (*it_item).value().toString(); ++it_item; } } // **************************************************************************** // // QueryTable implementation // // **************************************************************************** /** * TODO * * - Collapse 2- & 3- groups when they are identical * - Way more test cases (especially splits & transfers) * - Option to collapse splits * - Option to exclude transfers * */ QueryTable::QueryTable(const MyMoneyReport& _report): ListTable(_report) { // separated into its own method to allow debugging (setting breakpoints // directly in ctors somehow does not work for me (ipwizard)) // TODO: remove the init() method and move the code back to the ctor init(); } void QueryTable::init() { m_columns.clear(); m_group.clear(); m_subtotal.clear(); m_postcolumns.clear(); switch (m_config.rowType()) { case MyMoneyReport::eAccountByTopAccount: case MyMoneyReport::eEquityType: case MyMoneyReport::eAccountType: case MyMoneyReport::eInstitution: constructAccountTable(); m_columns << ctAccount; break; case MyMoneyReport::eAccount: constructTransactionTable(); m_columns << ctAccountID << ctPostDate; break; case MyMoneyReport::ePayee: case MyMoneyReport::eTag: case MyMoneyReport::eMonth: case MyMoneyReport::eWeek: constructTransactionTable(); m_columns << ctPostDate << ctAccount; break; case MyMoneyReport::eCashFlow: constructSplitsTable(); m_columns << ctPostDate; break; default: constructTransactionTable(); m_columns << ctPostDate; } // Sort the data to match the report definition m_subtotal << ctValue; switch (m_config.rowType()) { case MyMoneyReport::eCashFlow: m_group << ctCategoryType << ctTopCategory << ctCategory; break; case MyMoneyReport::eCategory: m_group << ctCategoryType << ctTopCategory << ctCategory; break; case MyMoneyReport::eTopCategory: m_group << ctCategoryType << ctTopCategory; break; case MyMoneyReport::eTopAccount: m_group << ctTopAccount << ctAccount; break; case MyMoneyReport::eAccount: m_group << ctAccount; break; case MyMoneyReport::eAccountReconcile: m_group << ctAccount << ctReconcileFlag; break; case MyMoneyReport::ePayee: m_group << ctPayee; break; case MyMoneyReport::eTag: m_group << ctTag; break; case MyMoneyReport::eMonth: m_group << ctMonth; break; case MyMoneyReport::eWeek: m_group << ctWeek; break; case MyMoneyReport::eAccountByTopAccount: m_group << ctTopAccount; break; case MyMoneyReport::eEquityType: m_group << ctEquityType; break; case MyMoneyReport::eAccountType: m_group << ctType; break; case MyMoneyReport::eInstitution: m_group << ctInstitution << ctTopAccount; break; default: throw MYMONEYEXCEPTION("QueryTable::QueryTable(): unhandled row type"); } QVector sort = QVector::fromList(m_group) << QVector::fromList(m_columns) << ctID << ctRank; m_columns.clear(); switch (m_config.rowType()) { case MyMoneyReport::eAccountByTopAccount: case MyMoneyReport::eEquityType: case MyMoneyReport::eAccountType: case MyMoneyReport::eInstitution: m_columns << ctAccount; break; default: m_columns << ctPostDate; } unsigned qc = m_config.queryColumns(); if (qc & MyMoneyReport::eQCnumber) m_columns << ctNumber; if (qc & MyMoneyReport::eQCpayee) m_columns << ctPayee; if (qc & MyMoneyReport::eQCtag) m_columns << ctTag; if (qc & MyMoneyReport::eQCcategory) m_columns << ctCategory; if (qc & MyMoneyReport::eQCaccount) m_columns << ctAccount; if (qc & MyMoneyReport::eQCreconciled) m_columns << ctReconcileFlag; if (qc & MyMoneyReport::eQCmemo) m_columns << ctMemo; if (qc & MyMoneyReport::eQCaction) m_columns << ctAction; if (qc & MyMoneyReport::eQCshares) m_columns << ctShares; if (qc & MyMoneyReport::eQCprice) m_columns << ctPrice; if (qc & MyMoneyReport::eQCperformance) { m_subtotal.clear(); switch (m_config.investmentSum()) { case MyMoneyReport::eSumOwnedAndSold: m_columns << ctBuys << ctSells << ctReinvestIncome << ctCashIncome << ctEndingBalance << ctReturn << ctReturnInvestment; m_subtotal << ctBuys << ctSells << ctReinvestIncome << ctCashIncome << ctEndingBalance << ctReturn << ctReturnInvestment; break; case MyMoneyReport::eSumOwned: m_columns << ctBuys << ctReinvestIncome << ctMarketValue << ctReturn << ctReturnInvestment; m_subtotal << ctBuys << ctReinvestIncome << ctMarketValue << ctReturn << ctReturnInvestment; break; case MyMoneyReport::eSumSold: m_columns << ctBuys << ctSells << ctCashIncome << ctReturn << ctReturnInvestment; m_subtotal << ctBuys << ctSells << ctCashIncome << ctReturn << ctReturnInvestment; break; case MyMoneyReport::eSumPeriod: default: m_columns << ctStartingBalance << ctBuys << ctSells << ctReinvestIncome << ctCashIncome << ctEndingBalance << ctReturn << ctReturnInvestment; m_subtotal << ctStartingBalance << ctBuys << ctSells << ctReinvestIncome << ctCashIncome << ctEndingBalance << ctReturn << ctReturnInvestment; break; } } if (qc & MyMoneyReport::eQCcapitalgain) { m_subtotal.clear(); switch (m_config.investmentSum()) { case MyMoneyReport::eSumOwned: m_columns << ctShares << ctBuyPrice << ctLastPrice << ctBuys << ctMarketValue << ctPercentageGain << ctCapitalGain; m_subtotal << ctShares << ctBuyPrice << ctLastPrice << ctBuys << ctMarketValue << ctPercentageGain << ctCapitalGain; break; case MyMoneyReport::eSumSold: default: m_columns << ctBuys << ctSells << ctCapitalGain; m_subtotal << ctBuys << ctSells << ctCapitalGain; if (m_config.isShowingSTLTCapitalGains()) { m_columns << ctBuysST << ctSellsST << ctCapitalGainST << ctBuysLT << ctSellsLT << ctCapitalGainLT; m_subtotal << ctBuysST << ctSellsST << ctCapitalGainST << ctBuysLT << ctSellsLT << ctCapitalGainLT; } break; } } if (qc & MyMoneyReport::eQCloan) { m_columns << ctPayment << ctInterest << ctFees; m_postcolumns << ctBalance; } if (qc & MyMoneyReport::eQCbalance) m_postcolumns << ctBalance; TableRow::setSortCriteria(sort); qSort(m_rows); if (m_config.isShowingColumnTotals()) constructTotalRows(); // adds total rows to m_rows } void QueryTable::constructTotalRows() { if (m_rows.isEmpty()) return; // qSort places grand total at last position, because it doesn't belong to any group for (int i = 0; i < m_rows.count(); ++i) { if (m_rows.at(0)[ctRank] == QLatin1String("4") || m_rows.at(0)[ctRank] == QLatin1String("5")) // it should be unlikely that total row is at the top of rows, so... m_rows.move(0, m_rows.count() - 1 - i); // ...move it at the bottom else break; } MyMoneyFile* file = MyMoneyFile::instance(); QList subtotals = m_subtotal; QList groups = m_group; QList columns = m_columns; if (!m_subtotal.isEmpty() && subtotals.count() == 1) columns.append(m_subtotal); QList postcolumns = m_postcolumns; if (!m_postcolumns.isEmpty()) columns.append(postcolumns); QMap>> totalCurrency; QList> totalGroups; QMap totalsValues; // initialize all total values under summed columns to be zero foreach (auto subtotal, subtotals) { totalsValues.insert(subtotal, MyMoneyMoney()); } totalsValues.insert(ctRowsCount, MyMoneyMoney()); // create total groups containing totals row for each group totalGroups.append(totalsValues); // prepend with extra group for grand total for (int j = 0; j < groups.count(); ++j) { totalGroups.append(totalsValues); } QList stashedTotalRows; int iCurrentRow, iNextRow; for (iCurrentRow = 0; iCurrentRow < m_rows.count();) { iNextRow = iCurrentRow + 1; // total rows are useless at summing so remove whole block of them at once while (iNextRow != m_rows.count() && (m_rows.at(iNextRow).value(ctRank) == QLatin1String("4") || m_rows.at(iNextRow).value(ctRank) == QLatin1String("5"))) { stashedTotalRows.append(m_rows.takeAt(iNextRow)); // ...but stash them just in case } bool lastRow = (iNextRow == m_rows.count()); // sum all subtotal values for lowest group QString currencyID = m_rows.at(iCurrentRow).value(ctCurrency); if (m_rows.at(iCurrentRow).value(ctRank) == QLatin1String("1")) { // don't sum up on balance (rank = 0 || rank = 3) and minor split (rank = 2) foreach (auto subtotal, subtotals) { if (!totalCurrency.contains(currencyID)) totalCurrency[currencyID].append(totalGroups); totalCurrency[currencyID].last()[subtotal] += MyMoneyMoney(m_rows.at(iCurrentRow)[subtotal]); } totalCurrency[currencyID].last()[ctRowsCount] += MyMoneyMoney::ONE; } // iterate over groups from the lowest to the highest to find group change for (int i = groups.count() - 1; i >= 0 ; --i) { // if any of groups from next row changes (or next row is the last row), then it's time to put totals row if (lastRow || m_rows.at(iCurrentRow)[groups.at(i)] != m_rows.at(iNextRow)[groups.at(i)]) { bool isMainCurrencyTotal = true; QMap>>::iterator currencyGrp = totalCurrency.begin(); while (currencyGrp != totalCurrency.end()) { if (!MyMoneyMoney((*currencyGrp).at(i + 1).value(ctRowsCount)).isZero()) { // if no rows summed up, then no totals row TableRow totalsRow; // sum all subtotal values for higher groups (excluding grand total) and reset lowest group values QMap::iterator upperGrp = (*currencyGrp)[i].begin(); QMap::iterator lowerGrp = (*currencyGrp)[i + 1].begin(); while(upperGrp != (*currencyGrp)[i].end()) { totalsRow[lowerGrp.key()] = lowerGrp.value().toString(); // fill totals row with subtotal values... (*upperGrp) += (*lowerGrp); // (*lowerGrp) = MyMoneyMoney(); ++upperGrp; ++lowerGrp; } // custom total values calculations foreach (auto subtotal, subtotals) { if (subtotal == ctReturnInvestment) totalsRow[subtotal] = helperROI((*currencyGrp).at(i + 1).value(ctBuys) - (*currencyGrp).at(i + 1).value(ctReinvestIncome), (*currencyGrp).at(i + 1).value(ctSells), (*currencyGrp).at(i + 1).value(ctStartingBalance), (*currencyGrp).at(i + 1).value(ctEndingBalance) + (*currencyGrp).at(i + 1).value(ctMarketValue), (*currencyGrp).at(i + 1).value(ctCashIncome)).toString(); else if (subtotal == ctPercentageGain) totalsRow[subtotal] = (((*currencyGrp).at(i + 1).value(ctBuys) + (*currencyGrp).at(i + 1).value(ctMarketValue)) / (*currencyGrp).at(i + 1).value(ctBuys).abs()).toString(); else if (subtotal == ctPrice) totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(i + 1).value(ctPrice) / (*currencyGrp).at(i + 1).value(ctRowsCount)).toString(); } // total values that aren't calculated here, but are taken untouched from external source, e.g. constructPerformanceRow if (!stashedTotalRows.isEmpty()) { for (int j = 0; j < stashedTotalRows.count(); ++j) { if (stashedTotalRows.at(j).value(ctCurrency) != currencyID) continue; foreach (auto subtotal, subtotals) { if (subtotal == ctReturn) totalsRow[ctReturn] = stashedTotalRows.takeAt(j)[ctReturn]; } break; } } (*currencyGrp).replace(i + 1, totalsValues); for (int j = 0; j < groups.count(); ++j) { totalsRow[groups.at(j)] = m_rows.at(iCurrentRow)[groups.at(j)]; // ...and identification } QString currencyID = currencyGrp.key(); if (currencyID.isEmpty() && totalCurrency.count() > 1) currencyID = file->baseCurrency().id(); totalsRow[ctCurrency] = currencyID; if (isMainCurrencyTotal) { totalsRow[ctRank] = QLatin1Char('4'); isMainCurrencyTotal = false; } else totalsRow[ctRank] = QLatin1Char('5'); totalsRow[ctDepth] = QString::number(i); totalsRow.remove(ctRowsCount); m_rows.insert(iNextRow++, totalsRow); // iCurrentRow and iNextRow can diverge here by more than one } ++currencyGrp; } } } // code to put grand total row if (lastRow) { bool isMainCurrencyTotal = true; QMap>>::iterator currencyGrp = totalCurrency.begin(); while (currencyGrp != totalCurrency.end()) { TableRow totalsRow; QMap::const_iterator grandTotalGrp = (*currencyGrp)[0].constBegin(); while(grandTotalGrp != (*currencyGrp)[0].constEnd()) { totalsRow[grandTotalGrp.key()] = grandTotalGrp.value().toString(); ++grandTotalGrp; } foreach (auto subtotal, subtotals) { if (subtotal == ctReturnInvestment) totalsRow[subtotal] = helperROI((*currencyGrp).at(0).value(ctBuys) - (*currencyGrp).at(0).value(ctReinvestIncome), (*currencyGrp).at(0).value(ctSells), (*currencyGrp).at(0).value(ctStartingBalance), (*currencyGrp).at(0).value(ctEndingBalance) + (*currencyGrp).at(0).value(ctMarketValue), (*currencyGrp).at(0).value(ctCashIncome)).toString(); else if (subtotal == ctPercentageGain) totalsRow[subtotal] = (((*currencyGrp).at(0).value(ctBuys) + (*currencyGrp).at(0).value(ctMarketValue)) / (*currencyGrp).at(0).value(ctBuys).abs()).toString(); else if (subtotal == ctPrice) totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(0).value(ctPrice) / (*currencyGrp).at(0).value(ctRowsCount)).toString(); } if (!stashedTotalRows.isEmpty()) { for (int j = 0; j < stashedTotalRows.count(); ++j) { foreach (auto subtotal, subtotals) { if (subtotal == ctReturn) totalsRow[ctReturn] = stashedTotalRows.takeAt(j)[ctReturn]; } } } for (int j = 0; j < groups.count(); ++j) { totalsRow[groups.at(j)] = QString(); // no identification } QString currencyID = currencyGrp.key(); if (currencyID.isEmpty() && totalCurrency.count() > 1) currencyID = file->baseCurrency().id(); totalsRow[ctCurrency] = currencyID; if (isMainCurrencyTotal) { totalsRow[ctRank] = QLatin1Char('4'); isMainCurrencyTotal = false; } else totalsRow[ctRank] = QLatin1Char('5'); totalsRow[ctDepth] = QString(); m_rows.append(totalsRow); ++currencyGrp; } break; // no use to loop further } iCurrentRow = iNextRow; // iCurrent makes here a leap forward by at least one } } void QueryTable::constructTransactionTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); MyMoneyReport report(m_config); report.setReportAllSplits(false); report.setConsiderCategory(true); bool use_transfers; bool use_summary; bool hide_details; bool tag_special_case = false; switch (m_config.rowType()) { case MyMoneyReport::eCategory: case MyMoneyReport::eTopCategory: use_summary = false; use_transfers = false; hide_details = false; break; case MyMoneyReport::ePayee: use_summary = false; use_transfers = false; hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone); break; case MyMoneyReport::eTag: use_summary = false; use_transfers = false; hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone); tag_special_case = true; break; default: use_summary = true; use_transfers = true; hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone); break; } // support for opening and closing balances QMap accts; //get all transactions for this report QList transactions = file->transactionList(report); for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) { TableRow qA, qS; QDate pd; QList tagIdListCache; qA[ctID] = qS[ctID] = (* it_transaction).id(); qA[ctEntryDate] = qS[ctEntryDate] = (* it_transaction).entryDate().toString(Qt::ISODate); qA[ctPostDate] = qS[ctPostDate] = (* it_transaction).postDate().toString(Qt::ISODate); qA[ctCommodity] = qS[ctCommodity] = (* it_transaction).commodity(); pd = (* it_transaction).postDate(); qA[ctMonth] = qS[ctMonth] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); qA[ctWeek] = qS[ctWeek] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); if (!m_containsNonBaseCurrency && (*it_transaction).commodity() != file->baseCurrency().id()) m_containsNonBaseCurrency = true; if (report.isConvertCurrency()) qA[ctCurrency] = qS[ctCurrency] = file->baseCurrency().id(); else qA[ctCurrency] = qS[ctCurrency] = (*it_transaction).commodity(); // to handle splits, we decide on which account to base the split // (a reference point or point of view so to speak). here we take the // first account that is a stock account or loan account (or the first account // that is not an income or expense account if there is no stock or loan account) // to be the account (qA) that will have the sub-item "split" entries. we add // one transaction entry (qS) for each subsequent entry in the split. const QList& splits = (*it_transaction).splits(); QList::const_iterator myBegin, it_split; for (it_split = splits.constBegin(), myBegin = splits.constEnd(); it_split != splits.constEnd(); ++it_split) { - ReportAccount splitAcc = (* it_split).accountId(); + ReportAccount splitAcc((* it_split).accountId()); // always put split with a "stock" account if it exists if (splitAcc.isInvest()) break; // prefer to put splits with a "loan" account if it exists if (splitAcc.isLoan()) myBegin = it_split; if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) { myBegin = it_split; } } // select our "reference" split if (it_split == splits.end()) { it_split = myBegin; } else { myBegin = it_split; } // skip this transaction if we didn't find a valid base account - see the above description // for the base account's description - if we don't find it avoid a crash by skipping the transaction if (myBegin == splits.end()) continue; // if the split is still unknown, use the first one. I have seen this // happen with a transaction that has only a single split referencing an income or expense // account and has an amount and value of 0. Such a transaction will fall through // the above logic and leave 'it_split' pointing to splits.end() which causes the remainder // of this to end in an infinite loop. if (it_split == splits.end()) { it_split = splits.begin(); } // for "loan" reports, the loan transaction gets special treatment. // the splits of a loan transaction are placed on one line in the // reference (loan) account (qA). however, we process the matching // split entries (qS) normally. bool loan_special_case = false; if (m_config.queryColumns() & MyMoneyReport::eQCloan) { - ReportAccount splitAcc = (*it_split).accountId(); + ReportAccount splitAcc((*it_split).accountId()); loan_special_case = splitAcc.isLoan(); } bool include_me = true; bool transaction_text = false; //indicates whether a text should be considered as a match for the transaction or for a split only QString a_fullname; QString a_memo; int pass = 1; QString myBeginCurrency; QString baseCurrency = file->baseCurrency().id(); QMap xrMap; // container for conversion rates from given currency to myBeginCurrency do { MyMoneyMoney xr; - ReportAccount splitAcc = (* it_split).accountId(); + ReportAccount splitAcc((* it_split).accountId()); QString splitCurrency; if (splitAcc.isInvest()) splitCurrency = file->account(file->account((*it_split).accountId()).parentAccountId()).currencyId(); else splitCurrency = file->account((*it_split).accountId()).currencyId(); if (it_split == myBegin) myBeginCurrency = splitCurrency; //get fraction for account int fraction = splitAcc.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = splitAcc.institutionId(); QString payee = (*it_split).payeeId(); const QList tagIdList = (*it_split).tagIdList(); //convert to base currency if (m_config.isConvertCurrency()) { xr = xrMap.value(splitCurrency, xr); // check if there is conversion rate to myBeginCurrency already stored... if (xr == MyMoneyMoney()) // ...if not... xr = (*it_split).price(); // ...take conversion rate to myBeginCurrency from split else if (splitAcc.isInvest()) // if it's stock split... xr *= (*it_split).price(); // ...multiply it by stock price stored in split if (!m_containsNonBaseCurrency && myBeginCurrency != baseCurrency) m_containsNonBaseCurrency = true; if (myBeginCurrency != baseCurrency) { // myBeginCurrency can differ from baseCurrency... MyMoneyPrice price = file->price(myBeginCurrency, baseCurrency, (*it_transaction).postDate()); // ...so check conversion rate... if (price.isValid()) { xr *= price.rate(baseCurrency); // ...and multiply it by current price... qA[ctCurrency] = qS[ctCurrency] = baseCurrency; } else qA[ctCurrency] = qS[ctCurrency] = myBeginCurrency; // ...and set information about non-baseCurrency } } else if (splitAcc.isInvest()) xr = (*it_split).price(); else xr = MyMoneyMoney::ONE; if (it_split == myBegin) { include_me = m_config.includes(splitAcc); if (include_me) // track accts that will need opening and closing balances //FIXME in some cases it will show the opening and closing //balances but no transactions if the splits are all filtered out -- asoliverez accts.insert(splitAcc.id(), splitAcc); qA[ctAccount] = splitAcc.name(); qA[ctAccountID] = splitAcc.id(); qA[ctTopAccount] = splitAcc.topParentName(); if (splitAcc.isInvest()) { // use the institution of the parent for stock accounts institution = splitAcc.parent().institutionId(); MyMoneyMoney shares = (*it_split).shares(); int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision(); qA[ctAction] = (*it_split).action(); qA[ctShares] = shares.isZero() ? QString() : shares.toString(); qA[ctPrice] = shares.isZero() ? QString() : xr.convertPrecision(pricePrecision).toString(); if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && shares.isNegative()) qA[ctAction] = "Sell"; qA[ctInvestAccount] = splitAcc.parent().name(); MyMoneySplit stockSplit = (*it_split); MyMoneySplit assetAccountSplit; QList feeSplits; QList interestSplits; MyMoneySecurity currency; MyMoneySecurity security; eMyMoney::Split::InvestmentTransactionType transactionType; KMyMoneyUtils::dissectTransaction((*it_transaction), stockSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType); if (!(assetAccountSplit == MyMoneySplit())) { for (it_split = splits.begin(); it_split != splits.end(); ++it_split) { if ((*it_split) == assetAccountSplit) { - splitAcc = assetAccountSplit.accountId(); // switch over from stock split to asset split because amount in stock split doesn't take fees/interests into account + splitAcc = ReportAccount(assetAccountSplit.accountId()); // switch over from stock split to asset split because amount in stock split doesn't take fees/interests into account myBegin = it_split; // set myBegin to asset split, so stock split can be listed in details under splits myBeginCurrency = (file->account((*myBegin).accountId())).currencyId(); if (!m_containsNonBaseCurrency && myBeginCurrency != baseCurrency) m_containsNonBaseCurrency = true; if (m_config.isConvertCurrency()) { if (myBeginCurrency != baseCurrency) { MyMoneyPrice price = file->price(myBeginCurrency, baseCurrency, (*it_transaction).postDate()); if (price.isValid()) { xr = price.rate(baseCurrency); qA[ctCurrency] = qS[ctCurrency] = baseCurrency; } else qA[ctCurrency] = qS[ctCurrency] = myBeginCurrency; } else xr = MyMoneyMoney::ONE; qA[ctPrice] = shares.isZero() ? QString() : (stockSplit.price() * xr / (*it_split).price()).toString(); // put conversion rate for all splits with this currency, so... // every split of transaction have the same conversion rate xrMap.insert(splitCurrency, MyMoneyMoney::ONE / (*it_split).price()); } else xr = (*it_split).price(); break; } } } } else qA[ctPrice] = xr.toString(); a_fullname = splitAcc.fullName(); a_memo = (*it_split).memo(); transaction_text = m_config.match(&(*it_split)); qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qA[ctPayee] = payee.isEmpty() ? i18n("[Empty Payee]") : file->payee(payee).name().simplified(); if (tag_special_case) { tagIdListCache = tagIdList; } else { QString delimiter; foreach(const auto tagId, tagIdList) { qA[ctTag] += delimiter + file->tag(tagId).name().simplified(); delimiter = QLatin1Char(','); } } qA[ctReconcileDate] = (*it_split).reconcileDate().toString(Qt::ISODate); qA[ctReconcileFlag] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true); qA[ctNumber] = (*it_split).number(); qA[ctMemo] = a_memo; qA[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString(); qS[ctReconcileDate] = qA[ctReconcileDate]; qS[ctReconcileFlag] = qA[ctReconcileFlag]; qS[ctNumber] = qA[ctNumber]; qS[ctTopCategory] = splitAcc.topParentName(); qS[ctCategoryType] = i18n("Transfer"); // only include the configured accounts if (include_me) { if (loan_special_case) { // put the principal amount in the "value" column and convert to lowest fraction qA[ctValue] = (-(*it_split).shares() * xr).convert(fraction).toString(); qA[ctRank] = QLatin1Char('1'); qA[ctSplit].clear(); } else { if ((splits.count() > 2) && use_summary) { // add the "summarized" split transaction // this is the sub-total of the split detail // convert to lowest fraction qA[ctRank] = QLatin1Char('1'); qA[ctCategory] = i18n("[Split Transaction]"); qA[ctTopCategory] = i18nc("Split transaction", "Split"); qA[ctCategoryType] = i18nc("Split transaction", "Split"); m_rows += qA; } } } } else { if (include_me) { if (loan_special_case) { MyMoneyMoney value = (-(* it_split).shares() * xr).convert(fraction); if ((*it_split).action() == MyMoneySplit::ActionAmortization) { // put the payment in the "payment" column and convert to lowest fraction qA[ctPayee] = value.toString(); } else if ((*it_split).action() == MyMoneySplit::ActionInterest) { // put the interest in the "interest" column and convert to lowest fraction qA[ctInterest] = value.toString(); } else if (splits.count() > 2) { // [dv: This comment carried from the original code. I am // not exactly clear on what it means or why we do this.] // Put the initial pay-in nowhere (that is, ignore it). This // is dangerous, though. The only way I can tell the initial // pay-in apart from fees is if there are only 2 splits in // the transaction. I wish there was a better way. } else { // accumulate everything else in the "fees" column MyMoneyMoney n0 = MyMoneyMoney(qA[ctFees]); qA[ctFees] = (n0 + value).toString(); } // we don't add qA here for a loan transaction. we'll add one // qA afer all of the split components have been processed. // (see below) } //--- special case to hide split transaction details else if (hide_details && (splits.count() > 2)) { // essentially, don't add any qA entries } //--- default case includes all transaction details else { //this is when the splits are going to be shown as children of the main split if ((splits.count() > 2) && use_summary) { qA[ctValue].clear(); //convert to lowest fraction qA[ctSplit] = (-(*it_split).shares() * xr).convert(fraction).toString(); qA[ctRank] = QLatin1Char('2'); } else { //this applies when the transaction has only 2 splits, or each split is going to be //shown separately, eg. transactions by category switch (m_config.rowType()) { case MyMoneyReport::eCategory: case MyMoneyReport::eTopCategory: if (splitAcc.isIncomeExpense()) qA[ctValue] = (-(*it_split).shares() * xr).convert(fraction).toString(); // needed for category reports, in case of multicurrency transaction it breaks it break; default: break; } qA[ctSplit].clear(); qA[ctRank] = QLatin1Char('1'); } qA [ctMemo] = (*it_split).memo(); if (!m_containsNonBaseCurrency && splitAcc.currencyId() != file->baseCurrency().id()) m_containsNonBaseCurrency = true; if (report.isConvertCurrency()) qS[ctCurrency] = file->baseCurrency().id(); else qS[ctCurrency] = splitAcc.currency().id(); if (! splitAcc.isIncomeExpense()) { qA[ctCategory] = ((*it_split).shares().isNegative()) ? i18n("Transfer from %1", splitAcc.fullName()) : i18n("Transfer to %1", splitAcc.fullName()); qA[ctTopCategory] = splitAcc.topParentName(); qA[ctCategoryType] = i18n("Transfer"); } else { qA [ctCategory] = splitAcc.fullName(); qA [ctTopCategory] = splitAcc.topParentName(); qA [ctCategoryType] = MyMoneyAccount::accountTypeToString(splitAcc.accountGroup()); } if (use_transfers || (splitAcc.isIncomeExpense() && m_config.includes(splitAcc))) { //if it matches the text of the main split of the transaction or //it matches this particular split, include it //otherwise, skip it //if the filter is "does not contain" exclude the split if it does not match //even it matches the whole split if ((m_config.isInvertingText() && m_config.match(&(*it_split))) || (!m_config.isInvertingText() && (transaction_text || m_config.match(&(*it_split))))) { if (tag_special_case) { if (!tagIdListCache.size()) qA[ctTag] = i18n("[No Tag]"); else for (int i = 0; i < tagIdListCache.size(); i++) { qA[ctTag] = file->tag(tagIdListCache[i]).name().simplified(); m_rows += qA; } } else { m_rows += qA; } } } } } if (m_config.includes(splitAcc) && use_transfers && !(splitAcc.isInvest() && include_me)) { // otherwise stock split is displayed twice in report if (! splitAcc.isIncomeExpense()) { //multiply by currency and convert to lowest fraction qS[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString(); qS[ctRank] = QLatin1Char('1'); qS[ctAccount] = splitAcc.name(); qS[ctAccountID] = splitAcc.id(); qS[ctTopAccount] = splitAcc.topParentName(); qS[ctCategory] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", a_fullname) : i18n("Transfer from %1", a_fullname); qS[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qS[ctMemo] = (*it_split).memo().isEmpty() ? a_memo : (*it_split).memo(); //FIXME-ALEX When is used this? I can't find in which condition we arrive here... maybe this code is useless? QString delimiter; for (int i = 0; i < tagIdList.size(); i++) { qA[ctTag] += delimiter + file->tag(tagIdList[i]).name().simplified(); delimiter = "+"; } qS[ctPayee] = payee.isEmpty() ? qA[ctPayee] : file->payee(payee).name().simplified(); //check the specific split against the filter for text and amount //TODO this should be done at the engine, but I have no clear idea how -- asoliverez //if the filter is "does not contain" exclude the split if it does not match //even it matches the whole split if ((m_config.isInvertingText() && m_config.match(&(*it_split))) || (!m_config.isInvertingText() && (transaction_text || m_config.match(&(*it_split))))) { m_rows += qS; // track accts that will need opening and closing balances accts.insert(splitAcc.id(), splitAcc); } } } } ++it_split; // look for wrap-around if (it_split == splits.end()) it_split = splits.begin(); // but terminate if this transaction has only a single split if (splits.count() < 2) break; //check if there have been more passes than there are splits //this is to prevent infinite loops in cases of data inconsistency -- asoliverez ++pass; if (pass > splits.count()) break; } while (it_split != myBegin); if (loan_special_case) { m_rows += qA; } } // now run through our accts list and add opening and closing balances switch (m_config.rowType()) { case MyMoneyReport::eAccount: case MyMoneyReport::eTopAccount: break; // case MyMoneyReport::eCategory: // case MyMoneyReport::eTopCategory: // case MyMoneyReport::ePayee: // case MyMoneyReport::eMonth: // case MyMoneyReport::eWeek: default: return; } QDate startDate, endDate; report.validDateRange(startDate, endDate); QString strStartDate = startDate.toString(Qt::ISODate); QString strEndDate = endDate.toString(Qt::ISODate); startDate = startDate.addDays(-1); QMap::const_iterator it_account, accts_end; for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) { TableRow qA; - ReportAccount account = (* it_account); + ReportAccount account(*it_account); //get fraction for account int fraction = account.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = account.institutionId(); // use the institution of the parent for stock accounts if (account.isInvest()) institution = account.parent().institutionId(); MyMoneyMoney startBalance, endBalance, startPrice, endPrice; MyMoneyMoney startShares, endShares; //get price and convert currency if necessary if (m_config.isConvertCurrency()) { startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce(); endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce(); } else { startPrice = account.deepCurrencyPrice(startDate).reduce(); endPrice = account.deepCurrencyPrice(endDate).reduce(); } startShares = file->balance(account.id(), startDate); endShares = file->balance(account.id(), endDate); //get starting and ending balances startBalance = startShares * startPrice; endBalance = endShares * endPrice; //starting balance // don't show currency if we're converting or if it's not foreign if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id()) m_containsNonBaseCurrency = true; if (m_config.isConvertCurrency()) qA[ctCurrency] = file->baseCurrency().id(); else qA[ctCurrency] = account.currency().id(); qA[ctAccountID] = account.id(); qA[ctAccount] = account.name(); qA[ctTopAccount] = account.topParentName(); qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qA[ctRank] = QLatin1Char('0'); qA[ctPrice] = startPrice.convertPrecision(account.currency().pricePrecision()).toString(); if (account.isInvest()) { qA[ctShares] = startShares.toString(); } qA[ctPostDate] = strStartDate; qA[ctBalance] = startBalance.convert(fraction).toString(); qA[ctValue].clear(); qA[ctID] = QLatin1Char('A'); m_rows += qA; //ending balance qA[ctPrice] = endPrice.convertPrecision(account.currency().pricePrecision()).toString(); if (account.isInvest()) { qA[ctShares] = endShares.toString(); } qA[ctPostDate] = strEndDate; qA[ctBalance] = endBalance.toString(); qA[ctRank] = QLatin1Char('3'); qA[ctID] = QLatin1Char('Z'); m_rows += qA; } } MyMoneyMoney QueryTable::helperROI(const MyMoneyMoney &buys, const MyMoneyMoney &sells, const MyMoneyMoney &startingBal, const MyMoneyMoney &endingBal, const MyMoneyMoney &cashIncome) const { MyMoneyMoney returnInvestment; if (!buys.isZero() || !startingBal.isZero()) { returnInvestment = (sells + buys + cashIncome + endingBal - startingBal) / (startingBal - buys); returnInvestment = returnInvestment.convert(10000); } else returnInvestment = MyMoneyMoney(); // if no investment then no return on investment return returnInvestment; } MyMoneyMoney QueryTable::helperIRR(const CashFlowList &all) const { MyMoneyMoney annualReturn; try { double irr = all.IRR(); #ifdef Q_CC_MSVC annualReturn = MyMoneyMoney(_isnan(irr) ? 0 : irr, 10000); #else annualReturn = MyMoneyMoney(std::isnan(irr) ? 0 : irr, 10000); #endif } catch (QString e) { qDebug() << e; } return annualReturn; } void QueryTable::sumInvestmentValues(const ReportAccount& account, QList& cfList, QList& shList) const { for (int i = InvestmentValue::Buys; i < InvestmentValue::End; ++i) cfList.append(CashFlowList()); for (int i = InvestmentValue::Buys; i <= InvestmentValue::BuysOfOwned; ++i) shList.append(MyMoneyMoney()); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyReport report = m_config; QDate startingDate; QDate endingDate; QDate newStartingDate; QDate newEndingDate; const bool isSTLT = report.isShowingSTLTCapitalGains(); const int settlementPeriod = report.settlementPeriod(); QDate termSeparator = report.termSeparator().addDays(-settlementPeriod); report.validDateRange(startingDate, endingDate); newStartingDate = startingDate; newEndingDate = endingDate; if (report.queryColumns() & MyMoneyReport::eQCcapitalgain) { // Saturday and Sunday aren't valid settlement dates if (endingDate.dayOfWeek() == Qt::Saturday) endingDate = endingDate.addDays(-1); else if (endingDate.dayOfWeek() == Qt::Sunday) endingDate = endingDate.addDays(-2); if (termSeparator.dayOfWeek() == Qt::Saturday) termSeparator = termSeparator.addDays(-1); else if (termSeparator.dayOfWeek() == Qt::Sunday) termSeparator = termSeparator.addDays(-2); if (startingDate.daysTo(endingDate) <= settlementPeriod) // no days to check for return; termSeparator = termSeparator.addDays(-settlementPeriod); newEndingDate = endingDate.addDays(-settlementPeriod); } shList[BuysOfOwned] = file->balance(account.id(), newEndingDate); // get how many shares there are at the end of period MyMoneyMoney stashedBuysOfOwned = shList.at(BuysOfOwned); bool reportedDateRange = true; // flag marking sell transactions between startingDate and endingDate report.setReportAllSplits(false); report.setConsiderCategory(true); report.clearAccountFilter(); report.addAccount(account.id()); report.setDateFilter(newStartingDate, newEndingDate); do { QList transactions = file->transactionList(report); for (QList::const_reverse_iterator it_t = transactions.crbegin(); it_t != transactions.crend(); ++it_t) { MyMoneySplit shareSplit = (*it_t).splitByAccount(account.id()); MyMoneySplit assetAccountSplit; QList feeSplits; QList interestSplits; MyMoneySecurity security; MyMoneySecurity currency; eMyMoney::Split::InvestmentTransactionType transactionType; KMyMoneyUtils::dissectTransaction((*it_t), shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType); QDate postDate = (*it_t).postDate(); MyMoneyMoney price; //get price for the day of the transaction if we have to calculate base currency //we are using the value of the split which is in deep currency if (m_config.isConvertCurrency()) price = account.baseCurrencyPrice(postDate); //we only need base currency because the value is in deep currency else price = MyMoneyMoney::ONE; MyMoneyMoney value = assetAccountSplit.value() * price; MyMoneyMoney shares = shareSplit.shares(); if (transactionType == eMyMoney::Split::InvestmentTransactionType::BuyShares) { if (reportedDateRange) { cfList[Buys].append(CashFlowListItem(postDate, value)); shList[Buys] += shares; } if (shList.at(BuysOfOwned).isZero()) { // add sold shares if (shList.at(BuysOfSells) + shares > shList.at(Sells).abs()) { // add partially sold shares MyMoneyMoney tempVal = (((shList.at(Sells).abs() - shList.at(BuysOfSells))) / shares) * value; cfList[BuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[BuysOfSells] = shList.at(Sells).abs(); if (isSTLT && postDate < termSeparator) { cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[LongTermBuysOfSells] = shList.at(BuysOfSells); } } else { // add wholly sold shares cfList[BuysOfSells].append(CashFlowListItem(postDate, value)); shList[BuysOfSells] += shares; if (isSTLT && postDate < termSeparator) { cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, value)); shList[LongTermBuysOfSells] += shares; } } } else if (shList.at(BuysOfOwned) >= shares) { // substract not-sold shares shList[BuysOfOwned] -= shares; cfList[BuysOfOwned].append(CashFlowListItem(postDate, value)); } else { // substract partially not-sold shares MyMoneyMoney tempVal = ((shares - shList.at(BuysOfOwned)) / shares) * value; MyMoneyMoney tempVal2 = (shares - shList.at(BuysOfOwned)); cfList[BuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[BuysOfSells] += tempVal2; if (isSTLT && postDate < termSeparator) { cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[LongTermBuysOfSells] += tempVal2; } cfList[BuysOfOwned].append(CashFlowListItem(postDate, (shList.at(BuysOfOwned) / shares) * value)); shList[BuysOfOwned] = MyMoneyMoney(); } } else if (transactionType == eMyMoney::Split::InvestmentTransactionType::SellShares && reportedDateRange) { cfList[Sells].append(CashFlowListItem(postDate, value)); shList[Sells] += shares; } else if (transactionType == eMyMoney::Split::InvestmentTransactionType::SplitShares) { // shares variable is denominator of split ratio here for (int i = Buys; i <= InvestmentValue::BuysOfOwned; ++i) shList[i] /= shares; } else if (transactionType == eMyMoney::Split::InvestmentTransactionType::AddShares || // added shares, when sold give 100% capital gain transactionType == eMyMoney::Split::InvestmentTransactionType::ReinvestDividend) { if (shList.at(BuysOfOwned).isZero()) { // add added/reinvested shares if (shList.at(BuysOfSells) + shares > shList.at(Sells).abs()) { // add partially added/reinvested shares shList[BuysOfSells] = shList.at(Sells).abs(); if (postDate < termSeparator) shList[LongTermBuysOfSells] = shList[BuysOfSells]; } else { // add wholly added/reinvested shares shList[BuysOfSells] += shares; if (postDate < termSeparator) shList[LongTermBuysOfSells] += shares; } } else if (shList.at(BuysOfOwned) >= shares) { // substract not-added/not-reinvested shares shList[BuysOfOwned] -= shares; cfList[BuysOfOwned].append(CashFlowListItem(postDate, value)); } else { // substract partially not-added/not-reinvested shares MyMoneyMoney tempVal = (shares - shList.at(BuysOfOwned)); shList[BuysOfSells] += tempVal; if (postDate < termSeparator) shList[LongTermBuysOfSells] += tempVal; cfList[BuysOfOwned].append(CashFlowListItem(postDate, (shList.at(BuysOfOwned) / shares) * value)); shList[BuysOfOwned] = MyMoneyMoney(); } if (transactionType == eMyMoney::Split::InvestmentTransactionType::ReinvestDividend) { value = MyMoneyMoney(); foreach (const auto split, interestSplits) value += split.value(); value *= price; cfList[ReinvestIncome].append(CashFlowListItem(postDate, -value)); } } else if (transactionType == eMyMoney::Split::InvestmentTransactionType::RemoveShares && reportedDateRange) // removed shares give no value in return so no capital gain on them shList[Sells] += shares; else if (transactionType == eMyMoney::Split::InvestmentTransactionType::Dividend || transactionType == eMyMoney::Split::InvestmentTransactionType::Yield) cfList[CashIncome].append(CashFlowListItem(postDate, value)); } reportedDateRange = false; newEndingDate = newStartingDate; newStartingDate = newStartingDate.addYears(-1); report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier } while ( ( (report.investmentSum() == MyMoneyReport::eSumOwned && !shList[BuysOfOwned].isZero()) || (report.investmentSum() == MyMoneyReport::eSumSold && !shList.at(Sells).isZero() && shList.at(Sells).abs() > shList.at(BuysOfSells).abs()) || (report.investmentSum() == MyMoneyReport::eSumOwnedAndSold && (!shList[BuysOfOwned].isZero() || (!shList.at(Sells).isZero() && shList.at(Sells).abs() > shList.at(BuysOfSells).abs()))) ) && account.openingDate() <= newEndingDate ); // we've got buy value and no sell value of long-term shares, so get them if (isSTLT && !shList[LongTermBuysOfSells].isZero()) { newStartingDate = startingDate; newEndingDate = endingDate.addDays(-settlementPeriod); report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier QList transactions = file->transactionList(report); shList[BuysOfOwned] = shList[LongTermBuysOfSells]; foreach (const auto transaction, transactions) { MyMoneySplit shareSplit = transaction.splitByAccount(account.id()); MyMoneySplit assetAccountSplit; QList feeSplits; QList interestSplits; MyMoneySecurity security; MyMoneySecurity currency; eMyMoney::Split::InvestmentTransactionType transactionType; KMyMoneyUtils::dissectTransaction(transaction, shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType); QDate postDate = transaction.postDate(); MyMoneyMoney price; if (m_config.isConvertCurrency()) price = account.baseCurrencyPrice(postDate); //we only need base currency because the value is in deep currency else price = MyMoneyMoney::ONE; MyMoneyMoney value = assetAccountSplit.value() * price; MyMoneyMoney shares = shareSplit.shares(); if (transactionType == eMyMoney::Split::InvestmentTransactionType::SellShares) { if ((shList.at(LongTermSellsOfBuys) + shares).abs() >= shList.at(LongTermBuysOfSells)) { // add partially sold long-term shares cfList[LongTermSellsOfBuys].append(CashFlowListItem(postDate, (shList.at(LongTermSellsOfBuys).abs() - shList.at(LongTermBuysOfSells)) / shares * value)); shList[LongTermSellsOfBuys] = shList.at(LongTermBuysOfSells); break; } else { // add wholly sold long-term shares cfList[LongTermSellsOfBuys].append(CashFlowListItem(postDate, value)); shList[LongTermSellsOfBuys] += shares; } } else if (transactionType == eMyMoney::Split::InvestmentTransactionType::RemoveShares) { if ((shList.at(LongTermSellsOfBuys) + shares).abs() >= shList.at(LongTermBuysOfSells)) { shList[LongTermSellsOfBuys] = shList.at(LongTermBuysOfSells); break; } else shList[LongTermSellsOfBuys] += shares; } } } shList[BuysOfOwned] = stashedBuysOfOwned; report.setDateFilter(startingDate, endingDate); // reset data filter for next security return; } void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result, CashFlowList &all) const { MyMoneyReport report = m_config; QDate startingDate; QDate endingDate; report.validDateRange(startingDate, endingDate); startingDate = startingDate.addDays(-1); MyMoneyFile* file = MyMoneyFile::instance(); //get fraction depending on type of account int fraction = account.currency().smallestAccountFraction(); MyMoneyMoney price; if (m_config.isConvertCurrency()) price = account.deepCurrencyPrice(startingDate) * account.baseCurrencyPrice(startingDate); else price = account.deepCurrencyPrice(startingDate); MyMoneyMoney startingBal = file->balance(account.id(), startingDate) * price; //convert to lowest fraction startingBal = startingBal.convert(fraction); //calculate ending balance if (m_config.isConvertCurrency()) price = account.deepCurrencyPrice(endingDate) * account.baseCurrencyPrice(endingDate); else price = account.deepCurrencyPrice(endingDate); MyMoneyMoney endingBal = file->balance((account).id(), endingDate) * price; //convert to lowest fraction endingBal = endingBal.convert(fraction); QList cfList; QList shList; sumInvestmentValues(account, cfList, shList); MyMoneyMoney buysTotal; MyMoneyMoney sellsTotal; MyMoneyMoney cashIncomeTotal; MyMoneyMoney reinvestIncomeTotal; switch (m_config.investmentSum()) { case MyMoneyReport::eSumOwnedAndSold: buysTotal = cfList.at(BuysOfSells).total() + cfList.at(BuysOfOwned).total(); sellsTotal = cfList.at(Sells).total(); cashIncomeTotal = cfList.at(CashIncome).total(); reinvestIncomeTotal = cfList.at(ReinvestIncome).total(); startingBal = MyMoneyMoney(); if (buysTotal.isZero() && sellsTotal.isZero() && cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero()) return; all.append(cfList.at(BuysOfSells)); all.append(cfList.at(BuysOfOwned)); all.append(cfList.at(Sells)); all.append(cfList.at(CashIncome)); result[ctSells] = sellsTotal.toString(); result[ctCashIncome] = cashIncomeTotal.toString(); result[ctReinvestIncome] = reinvestIncomeTotal.toString(); result[ctEndingBalance] = endingBal.toString(); break; case MyMoneyReport::eSumOwned: buysTotal = cfList.at(BuysOfOwned).total(); startingBal = MyMoneyMoney(); if (buysTotal.isZero() && endingBal.isZero()) return; all.append(cfList.at(BuysOfOwned)); all.append(CashFlowListItem(endingDate, endingBal)); result[ctReinvestIncome] = reinvestIncomeTotal.toString(); result[ctMarketValue] = endingBal.toString(); break; case MyMoneyReport::eSumSold: buysTotal = cfList.at(BuysOfSells).total(); sellsTotal = cfList.at(Sells).total(); cashIncomeTotal = cfList.at(CashIncome).total(); startingBal = endingBal = MyMoneyMoney(); // check if there are any meaningfull values before adding them to results if (buysTotal.isZero() && sellsTotal.isZero() && cashIncomeTotal.isZero()) return; all.append(cfList.at(BuysOfSells)); all.append(cfList.at(Sells)); all.append(cfList.at(CashIncome)); result[ctSells] = sellsTotal.toString(); result[ctCashIncome] = cashIncomeTotal.toString(); break; case MyMoneyReport::eSumPeriod: default: buysTotal = cfList.at(Buys).total(); sellsTotal = cfList.at(Sells).total(); cashIncomeTotal = cfList.at(CashIncome).total(); reinvestIncomeTotal = cfList.at(ReinvestIncome).total(); if (buysTotal.isZero() && sellsTotal.isZero() && cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero() && startingBal.isZero() && endingBal.isZero()) return; all.append(cfList.at(Buys)); all.append(cfList.at(Sells)); all.append(cfList.at(CashIncome)); all.append(CashFlowListItem(startingDate, -startingBal)); all.append(CashFlowListItem(endingDate, endingBal)); result[ctSells] = sellsTotal.toString(); result[ctCashIncome] = cashIncomeTotal.toString(); result[ctReinvestIncome] = reinvestIncomeTotal.toString(); result[ctStartingBalance] = startingBal.toString(); result[ctEndingBalance] = endingBal.toString(); break; } MyMoneyMoney returnInvestment = helperROI(buysTotal - reinvestIncomeTotal, sellsTotal, startingBal, endingBal, cashIncomeTotal); MyMoneyMoney annualReturn = helperIRR(all); result[ctBuys] = buysTotal.toString(); result[ctReturn] = annualReturn.toString(); result[ctReturnInvestment] = returnInvestment.toString(); result[ctEquityType] = MyMoneySecurity::securityTypeToString(file->security(account.currencyId()).securityType()); } void QueryTable::constructCapitalGainRow(const ReportAccount& account, TableRow& result) const { MyMoneyFile* file = MyMoneyFile::instance(); QList cfList; QList shList; sumInvestmentValues(account, cfList, shList); MyMoneyMoney buysTotal = cfList.at(BuysOfSells).total(); MyMoneyMoney sellsTotal = cfList.at(Sells).total(); MyMoneyMoney longTermBuysOfSellsTotal = cfList.at(LongTermBuysOfSells).total(); MyMoneyMoney longTermSellsOfBuys = cfList.at(LongTermSellsOfBuys).total(); switch (m_config.investmentSum()) { case MyMoneyReport::eSumOwned: { if (shList.at(BuysOfOwned).isZero()) return; MyMoneyReport report = m_config; QDate startingDate; QDate endingDate; report.validDateRange(startingDate, endingDate); //get fraction depending on type of account int fraction = account.currency().smallestAccountFraction(); MyMoneyMoney price; //calculate ending balance if (m_config.isConvertCurrency()) price = account.deepCurrencyPrice(endingDate) * account.baseCurrencyPrice(endingDate); else price = account.deepCurrencyPrice(endingDate); MyMoneyMoney endingBal = shList.at(BuysOfOwned) * price; //convert to lowest fraction endingBal = endingBal.convert(fraction); buysTotal = cfList.at(BuysOfOwned).total() - cfList.at(ReinvestIncome).total(); int pricePrecision = file->security(account.currencyId()).pricePrecision(); result[ctBuys] = buysTotal.toString(); result[ctShares] = shList.at(BuysOfOwned).toString(); result[ctBuyPrice] = (buysTotal.abs() / shList.at(BuysOfOwned)).convertPrecision(pricePrecision).toString(); result[ctLastPrice] = price.toString(); result[ctMarketValue] = endingBal.toString(); result[ctCapitalGain] = (buysTotal + endingBal).toString(); result[ctPercentageGain] = ((buysTotal + endingBal)/buysTotal.abs()).toString(); break; } case MyMoneyReport::eSumSold: default: buysTotal = cfList.at(BuysOfSells).total() - cfList.at(ReinvestIncome).total(); sellsTotal = cfList.at(Sells).total(); longTermBuysOfSellsTotal = cfList.at(LongTermBuysOfSells).total(); longTermSellsOfBuys = cfList.at(LongTermSellsOfBuys).total(); // check if there are any meaningfull values before adding them to results if (buysTotal.isZero() && sellsTotal.isZero() && longTermBuysOfSellsTotal.isZero() && longTermSellsOfBuys.isZero()) return; result[ctBuys] = buysTotal.toString(); result[ctSells] = sellsTotal.toString(); result[ctCapitalGain] = (buysTotal + sellsTotal).toString(); if (m_config.isShowingSTLTCapitalGains()) { result[ctBuysLT] = longTermBuysOfSellsTotal.toString(); result[ctSellsLT] = longTermSellsOfBuys.toString(); result[ctCapitalGainLT] = (longTermBuysOfSellsTotal + longTermSellsOfBuys).toString(); result[ctBuysST] = (buysTotal - longTermBuysOfSellsTotal).toString(); result[ctSellsST] = (sellsTotal - longTermSellsOfBuys).toString(); result[ctCapitalGainST] = ((buysTotal - longTermBuysOfSellsTotal) + (sellsTotal - longTermSellsOfBuys)).toString(); } break; } result[ctEquityType] = MyMoneySecurity::securityTypeToString(file->security(account.currencyId()).securityType()); } void QueryTable::constructAccountTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); QMap> currencyCashFlow; // for total calculation QList accounts; file->accountList(accounts); for (auto it_account = accounts.constBegin(); it_account != accounts.constEnd(); ++it_account) { // Note, "Investment" accounts are never included in account rows because // they don't contain anything by themselves. In reports, they are only // useful as a "topaccount" aggregator of stock accounts if ((*it_account).isAssetLiability() && m_config.includes((*it_account)) && (*it_account).accountType() != eMyMoney::Account::Type::Investment) { // don't add the account if it is closed. In fact, the business logic // should prevent that an account can be closed with a balance not equal // to zero, but we never know. MyMoneyMoney shares = file->balance((*it_account).id(), m_config.toDate()); if (shares.isZero() && (*it_account).isClosed()) continue; ReportAccount account(*it_account); TableRow qaccountrow; CashFlowList accountCashflow; // for total calculation switch(m_config.queryColumns()) { case MyMoneyReport::eQCperformance: { constructPerformanceRow(account, qaccountrow, accountCashflow); if (!qaccountrow.isEmpty()) { // assuming that that report is grouped by topaccount qaccountrow[ctTopAccount] = account.topParentName(); if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id()) m_containsNonBaseCurrency = true; if (m_config.isConvertCurrency()) qaccountrow[ctCurrency] = file->baseCurrency().id(); else qaccountrow[ctCurrency] = account.currency().id(); if (!currencyCashFlow.value(qaccountrow.value(ctCurrency)).contains(qaccountrow.value(ctTopAccount))) currencyCashFlow[qaccountrow.value(ctCurrency)].insert(qaccountrow.value(ctTopAccount), accountCashflow); // create cashflow for unknown account... else currencyCashFlow[qaccountrow.value(ctCurrency)][qaccountrow.value(ctTopAccount)] += accountCashflow; // ...or add cashflow for known account } break; } case MyMoneyReport::eQCcapitalgain: constructCapitalGainRow(account, qaccountrow); break; default: { //get fraction for account int fraction = account.currency().smallestAccountFraction() != -1 ? account.currency().smallestAccountFraction() : file->baseCurrency().smallestAccountFraction(); MyMoneyMoney netprice = account.deepCurrencyPrice(m_config.toDate()); if (m_config.isConvertCurrency() && account.isForeignCurrency()) netprice *= account.baseCurrencyPrice(m_config.toDate()); // display currency is base currency, so set the price netprice = netprice.reduce(); shares = shares.reduce(); int pricePrecision = file->security(account.currencyId()).pricePrecision(); qaccountrow[ctPrice] = netprice.convertPrecision(pricePrecision).toString(); qaccountrow[ctValue] = (netprice * shares).convert(fraction).toString(); qaccountrow[ctShares] = shares.toString(); QString iid = account.institutionId(); // If an account does not have an institution, get it from the top-parent. if (iid.isEmpty() && !account.isTopLevel()) iid = account.topParent().institutionId(); if (iid.isEmpty()) qaccountrow[ctInstitution] = i18nc("No institution", "None"); else qaccountrow[ctInstitution] = file->institution(iid).name(); qaccountrow[ctType] = MyMoneyAccount::accountTypeToString(account.accountType()); } } if (qaccountrow.isEmpty()) // don't add the account if there are no calculated values continue; qaccountrow[ctRank] = QLatin1Char('1'); qaccountrow[ctAccount] = account.name(); qaccountrow[ctAccountID] = account.id(); qaccountrow[ctTopAccount] = account.topParentName(); if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id()) m_containsNonBaseCurrency = true; if (m_config.isConvertCurrency()) qaccountrow[ctCurrency] = file->baseCurrency().id(); else qaccountrow[ctCurrency] = account.currency().id(); m_rows.append(qaccountrow); } } if (m_config.queryColumns() == MyMoneyReport::eQCperformance && m_config.isShowingColumnTotals()) { TableRow qtotalsrow; qtotalsrow[ctRank] = QLatin1Char('4'); // add identification of row as total QMap currencyGrandCashFlow; QMap>::iterator currencyAccGrp = currencyCashFlow.begin(); while (currencyAccGrp != currencyCashFlow.end()) { // convert map of top accounts with cashflows to TableRow for (QMap::iterator topAccount = (*currencyAccGrp).begin(); topAccount != (*currencyAccGrp).end(); ++topAccount) { qtotalsrow[ctTopAccount] = topAccount.key(); qtotalsrow[ctReturn] = helperIRR(topAccount.value()).toString(); qtotalsrow[ctCurrency] = currencyAccGrp.key(); currencyGrandCashFlow[currencyAccGrp.key()] += topAccount.value(); // cumulative sum of cashflows of each topaccount m_rows.append(qtotalsrow); // rows aren't sorted yet, so no problem with adding them randomly at the end } ++currencyAccGrp; } QMap::iterator currencyGrp = currencyGrandCashFlow.begin(); qtotalsrow[ctTopAccount].clear(); // empty topaccount because it's grand cashflow while (currencyGrp != currencyGrandCashFlow.end()) { qtotalsrow[ctReturn] = helperIRR(currencyGrp.value()).toString(); qtotalsrow[ctCurrency] = currencyGrp.key(); m_rows.append(qtotalsrow); ++currencyGrp; } } } void QueryTable::constructSplitsTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); MyMoneyReport report(m_config); report.setReportAllSplits(false); report.setConsiderCategory(true); // support for opening and closing balances QMap accts; //get all transactions for this report QList transactions = file->transactionList(report); for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) { TableRow qA, qS; QDate pd; qA[ctID] = qS[ctID] = (* it_transaction).id(); qA[ctEntryDate] = qS[ctEntryDate] = (* it_transaction).entryDate().toString(Qt::ISODate); qA[ctPostDate] = qS[ctPostDate] = (* it_transaction).postDate().toString(Qt::ISODate); qA[ctCommodity] = qS[ctCommodity] = (* it_transaction).commodity(); pd = (* it_transaction).postDate(); qA[ctMonth] = qS[ctMonth] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); qA[ctWeek] = qS[ctWeek] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); if (!m_containsNonBaseCurrency && (*it_transaction).commodity() != file->baseCurrency().id()) m_containsNonBaseCurrency = true; if (report.isConvertCurrency()) qA[ctCurrency] = qS[ctCurrency] = file->baseCurrency().id(); else qA[ctCurrency] = qS[ctCurrency] = (*it_transaction).commodity(); // to handle splits, we decide on which account to base the split // (a reference point or point of view so to speak). here we take the // first account that is a stock account or loan account (or the first account // that is not an income or expense account if there is no stock or loan account) // to be the account (qA) that will have the sub-item "split" entries. we add // one transaction entry (qS) for each subsequent entry in the split. const QList& splits = (*it_transaction).splits(); QList::const_iterator myBegin, it_split; //S_end = splits.end(); for (it_split = splits.constBegin(), myBegin = splits.constEnd(); it_split != splits.constEnd(); ++it_split) { - ReportAccount splitAcc = (* it_split).accountId(); + ReportAccount splitAcc((* it_split).accountId()); // always put split with a "stock" account if it exists if (splitAcc.isInvest()) break; // prefer to put splits with a "loan" account if it exists if (splitAcc.isLoan()) myBegin = it_split; if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) { myBegin = it_split; } } // select our "reference" split if (it_split == splits.end()) { it_split = myBegin; } else { myBegin = it_split; } // if the split is still unknown, use the first one. I have seen this // happen with a transaction that has only a single split referencing an income or expense // account and has an amount and value of 0. Such a transaction will fall through // the above logic and leave 'it_split' pointing to splits.end() which causes the remainder // of this to end in an infinite loop. if (it_split == splits.end()) { it_split = splits.begin(); } // for "loan" reports, the loan transaction gets special treatment. // the splits of a loan transaction are placed on one line in the // reference (loan) account (qA). however, we process the matching // split entries (qS) normally. bool loan_special_case = false; if (m_config.queryColumns() & MyMoneyReport::eQCloan) { - ReportAccount splitAcc = (*it_split).accountId(); + ReportAccount splitAcc((*it_split).accountId()); loan_special_case = splitAcc.isLoan(); } // There is a slight chance that at this point myBegin is still pointing to splits.end() if the // transaction only has income and expense splits (which should not happen). In that case, point // it to the first split if (myBegin == splits.end()) { myBegin = splits.begin(); } //the account of the beginning splits - ReportAccount myBeginAcc = (*myBegin).accountId(); + ReportAccount myBeginAcc((*myBegin).accountId()); bool include_me = true; QString a_fullname; QString a_memo; int pass = 1; do { MyMoneyMoney xr; - ReportAccount splitAcc = (* it_split).accountId(); + ReportAccount splitAcc((* it_split).accountId()); //get fraction for account int fraction = splitAcc.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = splitAcc.institutionId(); QString payee = (*it_split).payeeId(); const QList tagIdList = (*it_split).tagIdList(); if (m_config.isConvertCurrency()) { xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate()) * splitAcc.baseCurrencyPrice((*it_transaction).postDate())).reduce(); } else { xr = splitAcc.deepCurrencyPrice((*it_transaction).postDate()).reduce(); } // reverse the sign of incomes and expenses to keep consistency in the way it is displayed in other reports if (splitAcc.isIncomeExpense()) { xr = -xr; } if (splitAcc.isInvest()) { // use the institution of the parent for stock accounts institution = splitAcc.parent().institutionId(); MyMoneyMoney shares = (*it_split).shares(); int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision(); qA[ctAction] = (*it_split).action(); qA[ctShares] = shares.isZero() ? QString() : (*it_split).shares().toString(); qA[ctPrice] = shares.isZero() ? QString() : xr.convertPrecision(pricePrecision).toString(); if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && (*it_split).shares().isNegative()) qA[ctAction] = "Sell"; qA[ctInvestAccount] = splitAcc.parent().name(); } include_me = m_config.includes(splitAcc); a_fullname = splitAcc.fullName(); a_memo = (*it_split).memo(); int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision(); qA[ctPrice] = xr.convertPrecision(pricePrecision).toString(); qA[ctAccount] = splitAcc.name(); qA[ctAccountID] = splitAcc.id(); qA[ctTopAccount] = splitAcc.topParentName(); qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); //FIXME-ALEX Is this useless? Isn't constructSplitsTable called only for cashflow type report? QString delimiter; foreach(const auto tagId, tagIdList) { qA[ctTag] += delimiter + file->tag(tagId).name().simplified(); delimiter = QLatin1Char(','); } qA[ctPayee] = payee.isEmpty() ? i18n("[Empty Payee]") : file->payee(payee).name().simplified(); qA[ctReconcileDate] = (*it_split).reconcileDate().toString(Qt::ISODate); qA[ctReconcileFlag] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true); qA[ctNumber] = (*it_split).number(); qA[ctMemo] = a_memo; qS[ctReconcileDate] = qA[ctReconcileDate]; qS[ctReconcileFlag] = qA[ctReconcileFlag]; qS[ctNumber] = qA[ctNumber]; qS[ctTopCategory] = splitAcc.topParentName(); // only include the configured accounts if (include_me) { // add the "summarized" split transaction // this is the sub-total of the split detail // convert to lowest fraction qA[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString(); qA[ctRank] = QLatin1Char('1'); //fill in account information if (! splitAcc.isIncomeExpense() && it_split != myBegin) { qA[ctAccount] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", myBeginAcc.fullName()) : i18n("Transfer from %1", myBeginAcc.fullName()); } else if (it_split == myBegin) { //handle the main split if ((splits.count() > 2)) { //if it is the main split and has multiple splits, note that qA[ctAccount] = i18n("[Split Transaction]"); } else { //fill the account name of the second split QList::const_iterator tempSplit = splits.constBegin(); //there are supposed to be only 2 splits if we ever get here if (tempSplit == myBegin && splits.count() > 1) ++tempSplit; //show the name of the category, or "transfer to/from" if it as an account - ReportAccount tempSplitAcc = (*tempSplit).accountId(); + ReportAccount tempSplitAcc((*tempSplit).accountId()); if (! tempSplitAcc.isIncomeExpense()) { qA[ctAccount] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", tempSplitAcc.fullName()) : i18n("Transfer from %1", tempSplitAcc.fullName()); } else { qA[ctAccount] = tempSplitAcc.fullName(); } } } else { //in any other case, fill in the account name of the main split qA[ctAccount] = myBeginAcc.fullName(); } //category data is always the one of the split qA [ctCategory] = splitAcc.fullName(); qA [ctTopCategory] = splitAcc.topParentName(); qA [ctCategoryType] = MyMoneyAccount::accountTypeToString(splitAcc.accountGroup()); m_rows += qA; // track accts that will need opening and closing balances accts.insert(splitAcc.id(), splitAcc); } ++it_split; // look for wrap-around if (it_split == splits.end()) it_split = splits.begin(); //check if there have been more passes than there are splits //this is to prevent infinite loops in cases of data inconsistency -- asoliverez ++pass; if (pass > splits.count()) break; } while (it_split != myBegin); if (loan_special_case) { m_rows += qA; } } // now run through our accts list and add opening and closing balances switch (m_config.rowType()) { case MyMoneyReport::eAccount: case MyMoneyReport::eTopAccount: break; // case MyMoneyReport::eCategory: // case MyMoneyReport::eTopCategory: // case MyMoneyReport::ePayee: // case MyMoneyReport::eMonth: // case MyMoneyReport::eWeek: default: return; } QDate startDate, endDate; report.validDateRange(startDate, endDate); QString strStartDate = startDate.toString(Qt::ISODate); QString strEndDate = endDate.toString(Qt::ISODate); startDate = startDate.addDays(-1); QMap::const_iterator it_account, accts_end; for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) { TableRow qA; - ReportAccount account = (* it_account); + ReportAccount account((* it_account)); //get fraction for account int fraction = account.currency().smallestAccountFraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = file->baseCurrency().smallestAccountFraction(); QString institution = account.institutionId(); // use the institution of the parent for stock accounts if (account.isInvest()) institution = account.parent().institutionId(); MyMoneyMoney startBalance, endBalance, startPrice, endPrice; MyMoneyMoney startShares, endShares; //get price and convert currency if necessary if (m_config.isConvertCurrency()) { startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce(); endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce(); } else { startPrice = account.deepCurrencyPrice(startDate).reduce(); endPrice = account.deepCurrencyPrice(endDate).reduce(); } startShares = file->balance(account.id(), startDate); endShares = file->balance(account.id(), endDate); //get starting and ending balances startBalance = startShares * startPrice; endBalance = endShares * endPrice; //starting balance // don't show currency if we're converting or if it's not foreign if (!m_containsNonBaseCurrency && account.currency().id() != file->baseCurrency().id()) m_containsNonBaseCurrency = true; if (m_config.isConvertCurrency()) qA[ctCurrency] = file->baseCurrency().id(); else qA[ctCurrency] = account.currency().id(); qA[ctAccountID] = account.id(); qA[ctAccount] = account.name(); qA[ctTopAccount] = account.topParentName(); qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); qA[ctRank] = QLatin1Char('0'); int pricePrecision = file->security(account.currencyId()).pricePrecision(); qA[ctPrice] = startPrice.convertPrecision(pricePrecision).toString(); if (account.isInvest()) { qA[ctShares] = startShares.toString(); } qA[ctPostDate] = strStartDate; qA[ctBalance] = startBalance.convert(fraction).toString(); qA[ctValue].clear(); qA[ctID] = QLatin1Char('A'); m_rows += qA; qA[ctRank] = QLatin1Char('3'); //ending balance qA[ctPrice] = endPrice.convertPrecision(pricePrecision).toString(); if (account.isInvest()) { qA[ctShares] = endShares.toString(); } qA[ctPostDate] = strEndDate; qA[ctBalance] = endBalance.toString(); qA[ctID] = QLatin1Char('Z'); m_rows += qA; } } } diff --git a/kmymoney/reports/querytable.h b/kmymoney/reports/querytable.h index 5b5039aca..10f793898 100644 --- a/kmymoney/reports/querytable.h +++ b/kmymoney/reports/querytable.h @@ -1,167 +1,167 @@ /*************************************************************************** querytable.h ------------------- begin : Fri Jul 23 2004 copyright : (C) 2004-2005 by Ace Jones (C) 2007 Sascha Pfau ***************************************************************************/ /**************************************************************************** Contains code from the func_xirr and related methods of financial.cpp - KOffice 1.6 by Sascha Pfau. Sascha agreed to relicense those methods under GPLv2 or later. *****************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef QUERYTABLE_H #define QUERYTABLE_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "listtable.h" #include "mymoneymoney.h" class MyMoneyReport; namespace reports { class ReportAccount; class CashFlowList; /** * Calculates a query of information about the transaction database. * * This is a middle-layer class, between the UI and the engine. The * MyMoneyReport class holds only the CONFIGURATION parameters. This * class actually does the work of retrieving the data from the engine * and formatting it for the user. * * @author Ace Jones * * @short **/ class QueryTable : public ListTable { public: - QueryTable(const MyMoneyReport&); + explicit QueryTable(const MyMoneyReport&); void init(); protected: void constructAccountTable(); void constructTotalRows(); void constructTransactionTable(); void sumInvestmentValues(const ReportAccount &account, QList &cfList, QList &shList) const; void constructPerformanceRow(const ReportAccount& account, TableRow& result, CashFlowList &all) const; void constructCapitalGainRow(const ReportAccount& account, TableRow& result) const; MyMoneyMoney helperROI(const MyMoneyMoney& buys, const MyMoneyMoney& sells, const MyMoneyMoney& startingBal, const MyMoneyMoney& endingBal, const MyMoneyMoney& cashIncome) const; MyMoneyMoney helperIRR(const CashFlowList& all) const; void constructSplitsTable(); private: enum InvestmentValue {Buys = 0, Sells, BuysOfSells, SellsOfBuys, LongTermBuysOfSells, LongTermSellsOfBuys, BuysOfOwned, ReinvestIncome, CashIncome, End}; }; // // Cash Flow analysis tools for investment reports // class CashFlowListItem { public: CashFlowListItem() {} CashFlowListItem(const QDate& _date, const MyMoneyMoney& _value): m_date(_date), m_value(_value) {} bool operator<(const CashFlowListItem& _second) const { return m_date < _second.m_date; } bool operator<=(const CashFlowListItem& _second) const { return m_date <= _second.m_date; } bool operator>(const CashFlowListItem& _second) const { return m_date > _second.m_date; } const QDate& date() const { return m_date; } const MyMoneyMoney& value() const { return m_value; } MyMoneyMoney NPV(double _rate) const; static void setToday(const QDate& _today) { m_sToday = _today; } const QDate& today() const { return m_sToday; } private: QDate m_date; MyMoneyMoney m_value; static QDate m_sToday; }; class CashFlowList: public QList { public: CashFlowList() {} MyMoneyMoney NPV(double rate) const; double IRR() const; MyMoneyMoney total() const; void dumpDebug() const; /** * Function: XIRR * * Compute the internal rate of return for a non-periodic series of cash flows. * * XIRR ( Values; Dates; [ Guess = 0.1 ] ) **/ double calculateXIRR() const; protected: CashFlowListItem mostRecent() const; private: /** * helper: xirrResult * * args[0] = values * args[1] = dates **/ double xirrResult(double& rate) const; /** * * helper: xirrResultDerive * * args[0] = values * args[1] = dates **/ double xirrResultDerive(double& rate) const; }; } #endif // QUERYREPORT_H diff --git a/kmymoney/reports/reportaccount.h b/kmymoney/reports/reportaccount.h index aab5c7930..d67fd587f 100644 --- a/kmymoney/reports/reportaccount.h +++ b/kmymoney/reports/reportaccount.h @@ -1,242 +1,242 @@ /*************************************************************************** reportaccount.h ------------------- begin : Sat May 22 2004 copyright : (C) 2004-2005 by Ace Jones email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef REPORTACCOUNT_H #define REPORTACCOUNT_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyaccount.h" namespace reports { /** * This is a MyMoneyAccount as viewed from the reporting engine. * * All reporting methods should use ReportAccount INSTEAD OF * MyMoneyAccount at all times. * * The primary functionality this provides is a full chain of account * hierarchy that is easy to traverse. It's needed because the PivotTable * grid needs to store and sort by the full account hierarchy, while still * having access to the account itself for currency conversion. * * In addition, several other convenience functions are provided that may * be worth moving into MyMoneyAccount at some point. * * @author Ace Jones * * @short **/ class ReportAccount: public MyMoneyAccount { private: QStringList m_nameHierarchy; public: /** * Default constructor * * Needed to allow this object to be stored in a QMap. */ ReportAccount(); /** * Copy constructor * * Needed to allow this object to be stored in a QMap. */ ReportAccount(const ReportAccount&); /** * Regular constructor * * @param accountid Account which this account descriptor should be based off of */ - ReportAccount(const QString& accountid); + explicit ReportAccount(const QString& accountid); /** * Regular constructor * * @param accountid Account which this account descriptor should be based off of */ - ReportAccount(const MyMoneyAccount& accountid); + explicit ReportAccount(const MyMoneyAccount& accountid); /** * @param right The object to compare against * @return bool True if this account's fully-qualified hierarchy name * is less than that of the given qccount */ bool operator<(const ReportAccount& right) const; /** * Returns the price of this account's underlying currency on the indicated date, * translated into the account's deep currency * * There are three different currencies in play with a single Account: * - The underlying currency: What currency the account itself is denominated in * - The deep currency: The underlying currency's own underlying currency. This * is only a factor if the underlying currency of this account IS NOT a * currency itself, but is some other kind of security. In that case, the * underlying security has its own currency. The deep currency is the * currency of the underlying security. On the other hand, if the account * has a currency itself, then the deep currency == the underlying currency, * and this function will return 1.0. * - The base currency: The base currency of the user's overall file * * @param date The date in question * @param exactDate if @a true, the @a date must be exact, otherwise * the last known price prior to this date can also be used * @a false is the default * @return MyMoneyMoney The value of the account's currency on that date */ MyMoneyMoney deepCurrencyPrice(const QDate& date, bool exactDate = false) const; /** * Returns the price of this account's deep currency on the indicated date, * translated into the base currency * * @param date The date in question * @param exactDate if @a true, the @a date must be exact, otherwise * the last known price prior to this date can also be used * @a false is the default * @return MyMoneyMoney The value of the account's currency on that date */ MyMoneyMoney baseCurrencyPrice(const QDate& date, bool exactDate = false) const; /** * Returns the price of this account's deep currency on the indicated date, * translated into the base currency * * @param foreignCurrency The currency on which the price will be returned * @param date The date in question * @param exactDate if @a true, the @a date must be exact, otherwise * the last known price prior to this date can also be used * @a false is the default * @return MyMoneyMoney The value of the account's currency on that date */ MyMoneyMoney foreignCurrencyPrice(const QString foreignCurrency, const QDate& date, bool exactDate = false) const; /** * Fetch the trading symbol of this account's deep currency * * @return The account's currency trading currency object */ MyMoneySecurity currency() const; /** * Determine if this account's deep currency is different from the file's * base currency * * @return bool True if this account is in a foreign currency */ bool isForeignCurrency() const; /** * The name of only this account. No matter how deep the hierarchy, this * method only returns the last name in the list, which is the engine name] * of this account. * * @return QString The account's name */ QString name() const; /** * The entire hierarchy of this account descriptor * This is similar to debugName(), however debugName() is not guaranteed * to always look pretty, while fullName() is. So if the user is ever * going to see the results, use fullName(). * * @return QString The account's full hierarchy */ QString fullName() const; /** * The entire hierarchy of this account descriptor, suitable for displaying * in debugging output * * @return QString The account's full hierarchy (suitable for debugging) */ QString debugName() const; /** * Whether this account is a 'top level' parent account. This means that * it's parent is an account class, like asset, liability, expense or income * * @return bool True if this account is a top level parent account */ /*inline*/ bool isTopLevel() const; /** * Returns the name of the top level parent account * * (See isTopLevel for a definition of 'top level parent') * * @return QString The name of the top level parent account */ /*inline*/ QString topParentName() const; /** * Returns a report account containing the top parent account * * @return ReportAccount The account of the top parent */ ReportAccount topParent() const; /** * Returns a report account containing the immediate parent account * * @return ReportAccount The account of the immediate parent */ ReportAccount parent() const; /** * Returns the number of accounts in this account's hierarchy. If this is a * Top Category, it returns 1. If it's parent is a Top Category, returns 2, * etc. * * @return unsigned Hierarchy depth */ unsigned hierarchyDepth() const; /** * Returns whether this account is a liquid liability * */ bool isLiquidLiability() const; protected: /** * Calculates the full account hierarchy of this account */ void calculateAccountHierarchy(); }; } // end namespace reports #endif // REPORTACCOUNT_H diff --git a/kmymoney/reports/reportdebug.h b/kmymoney/reports/reportdebug.h index 83b319b79..e3493efae 100644 --- a/kmymoney/reports/reportdebug.h +++ b/kmymoney/reports/reportdebug.h @@ -1,88 +1,88 @@ /*************************************************************************** reportdebug.h ------------------- begin : Sat May 22 2004 copyright : (C) 2004-2005 by Ace Jones email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef REPORTDEBUG_H #define REPORTDEBUG_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes namespace reports { // define to enable massive debug logging to stderr #undef DEBUG_REPORTS // #define DEBUG_REPORTS #define DEBUG_ENABLED_BY_DEFAULT false #ifdef DEBUG_REPORTS // define to filter out account names & transaction amounts // DO NOT check into CVS with this defined!! It breaks all // unit tests. #undef DEBUG_HIDE_SENSITIVE #define DEBUG_ENTER(x) Debug ___DEBUG(x) #define DEBUG_OUTPUT(x) ___DEBUG.output(x) #define DEBUG_OUTPUT_IF(x,y) { if (x) ___DEBUG.output(y); } #define DEBUG_ENABLE(x) Debug::enable(x) #define DEBUG_ENABLE_KEY(x) Debug::setEnableKey(x) #ifdef DEBUG_HIDE_SENSITIVE #define DEBUG_SENSITIVE(x) QString("hidden") #else #define DEBUG_SENSITIVE(x) (x) #endif #else #define DEBUG_ENTER(x) #define DEBUG_OUTPUT(x) #define DEBUG_OUTPUT_IF(x,y) #define DEBUG_ENABLE(x) #define DEBUG_SENSITIVE(x) #endif class Debug { QString m_methodName; static QString m_sTabs; static bool m_sEnabled; bool m_enabled; static QString m_sEnableKey; public: - Debug(const QString& _name); + explicit Debug(const QString& _name); ~Debug(); void output(const QString& _text); static void enable(bool _e) { m_sEnabled = _e; } static void setEnableKey(const QString& _s) { m_sEnableKey = _s; } }; } // end namespace reports #endif // REPORTDEBUG_H diff --git a/kmymoney/reports/tests/pivottable-test.cpp b/kmymoney/reports/tests/pivottable-test.cpp index 45055572c..f72070cf5 100644 --- a/kmymoney/reports/tests/pivottable-test.cpp +++ b/kmymoney/reports/tests/pivottable-test.cpp @@ -1,1069 +1,1069 @@ /*************************************************************************** pivottabletest.cpp ------------------- copyright : (C) 2002-2005 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #include "pivottable-test.h" #include #include #include #include // DOH, mmreport.h uses this without including it!! #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneyreport.h" #include "mymoneystatement.h" #include "mymoneysplit.h" #include "mymoneystoragedump.h" #include "mymoneystoragexml.h" #include "pivottable.h" #include "reportstestcommon.h" using namespace reports; using namespace test; QTEST_GUILESS_MAIN(PivotTableTest) void PivotTableTest::init() { storage = new MyMoneySeqAccessMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Thomas Baumgart"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount(QString("Checking Account"), eMyMoney::Account::Type::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset); acCredit = makeAccount(QString("Credit Card"), eMyMoney::Account::Type::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability); acSolo = makeAccount(QString("Solo"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acParent = makeAccount(QString("Parent"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acChild = makeAccount(QString("Child"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acForeign = makeAccount(QString("Foreign"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acSecondChild = makeAccount(QString("Second Child"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acGrandChild1 = makeAccount(QString("Grand Child 1"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild); acGrandChild2 = makeAccount(QString("Grand Child 2"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void PivotTableTest::cleanup() { file->detachStorage(storage); delete storage; } void PivotTableTest::testNetWorthSingle() { try { MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2004, 7, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); writeTabletoCSV(networth_f); - QVERIFY(networth_f.m_grid["Asset"]["Checking Account"][acChecking][eActual][5] == moCheckingOpen); - QVERIFY(networth_f.m_grid["Asset"]["Checking Account"][acChecking][eActual][6] == moCheckingOpen); + QVERIFY(networth_f.m_grid["Asset"]["Checking Account"][ReportAccount(acChecking)][eActual][5] == moCheckingOpen); + QVERIFY(networth_f.m_grid["Asset"]["Checking Account"][ReportAccount(acChecking)][eActual][6] == moCheckingOpen); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"].m_total[eActual][5] == moCheckingOpen); QVERIFY(networth_f.m_grid.m_total[eActual][0] == moZero); QVERIFY(networth_f.m_grid.m_total[eActual][4] == moZero); QVERIFY(networth_f.m_grid.m_total[eActual][5] == moCheckingOpen); QVERIFY(networth_f.m_grid.m_total[eActual][6] == moCheckingOpen); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void PivotTableTest::testNetWorthOfsetting() { // Test the net worth report to make sure it picks up the opening balance for two // accounts opened during the period of the report, one asset & one liability. Test // that it calculates the totals correctly. MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); - QVERIFY(networth_f.m_grid["Liability"]["Credit Card"][acCredit][eActual][7] == -moCreditOpen); + QVERIFY(networth_f.m_grid["Liability"]["Credit Card"][ReportAccount(acCredit)][eActual][7] == -moCreditOpen); QVERIFY(networth_f.m_grid.m_total[eActual][0] == moZero); QVERIFY(networth_f.m_grid.m_total[eActual][12] == moCheckingOpen + moCreditOpen); } void PivotTableTest::testNetWorthOpeningPrior() { // Test the net worth report to make sure it's picking up opening balances PRIOR to // the period of the report. MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2005, 8, 1), QDate(2005, 12, 31)); filter.setName("Net Worth Opening Prior 1"); XMLandback(filter); PivotTable networth_f(filter); writeTabletoCSV(networth_f); QVERIFY(networth_f.m_grid["Liability"]["Credit Card"].m_total[eActual][0] == -moCreditOpen); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"].m_total[eActual][0] == moCheckingOpen); QVERIFY(networth_f.m_grid.m_total[eActual][0] == moCheckingOpen + moCreditOpen); QVERIFY(networth_f.m_grid.m_total[eActual][1] == moCheckingOpen + moCreditOpen); // Test the net worth report to make sure that transactions prior to the report // period are included in the opening balance TransactionHelper t1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acChecking, acChild); filter.setName("Net Worth Opening Prior 2"); PivotTable networth_f2(filter); writeTabletoCSV(networth_f2); MyMoneyMoney m1 = (networth_f2.m_grid["Liability"]["Credit Card"].m_total[eActual][1]); MyMoneyMoney m2 = (-moCreditOpen + moParent); QVERIFY((networth_f2.m_grid["Liability"]["Credit Card"].m_total[eActual][1]) == (-moCreditOpen + moParent)); QVERIFY(networth_f2.m_grid["Asset"]["Checking Account"].m_total[eActual][1] == moCheckingOpen - moChild); QVERIFY(networth_f2.m_grid.m_total[eActual][1] == moCheckingOpen + moCreditOpen - moChild - moParent); } void PivotTableTest::testNetWorthDateFilter() { // Test a net worth report whose period is prior to the time any accounts are open, // so the report should be zero. MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2004, 2, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); QVERIFY(networth_f.m_grid.m_total[eActual][1] == moZero); } void PivotTableTest::testNetWorthOpening() { MyMoneyMoney openingBalance(12000000); auto acBasicAccount = makeAccount(QString("Basic Account"), eMyMoney::Account::Type::Checkings, openingBalance, QDate(2016, 1, 1), acAsset); auto ctBasicIncome = makeAccount(QString("Basic Income"), eMyMoney::Account::Type::Income, MyMoneyMoney(), QDate(2016, 1, 1), acIncome); auto ctBasicExpense = makeAccount(QString("Basic Expense"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2016, 1, 1), acExpense); TransactionHelper t1(QDate(2016, 7, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-6200000), acBasicAccount, ctBasicIncome); TransactionHelper t2(QDate(2016, 8, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-200000), acBasicAccount, ctBasicIncome); TransactionHelper t3(QDate(2016, 9, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-200000), acBasicAccount, ctBasicIncome); TransactionHelper t4(QDate(2016, 10, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t5(QDate(2016, 11, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t6(QDate(2016, 12, 1), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(100000), acBasicAccount, ctBasicExpense); TransactionHelper t7(QDate(2017, 1, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t8(QDate(2017, 2, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t9(QDate(2017, 3, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t10(QDate(2017, 4, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t11(QDate(2017, 5, 1), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(4500000), acBasicAccount, ctBasicExpense); TransactionHelper t12(QDate(2017, 6, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t13(QDate(2017, 7, 1), MyMoneySplit::ActionDeposit, MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2016, 1, 1), QDate(2017, 12, 31)); filter.addAccount(acBasicAccount); XMLandback(filter); PivotTable nt_opening1(filter); writeTabletoCSV(nt_opening1, "networth-opening-1.csv"); - QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][acBasicAccount][eActual][0] == MyMoneyMoney()); // opening value on 1st Jan 2016 is 12000000, but before that i.e. 31st Dec 2015 opening value is 0 + QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][0] == MyMoneyMoney()); // opening value on 1st Jan 2016 is 12000000, but before that i.e. 31st Dec 2015 opening value is 0 for (auto i = 1; i <= 6; ++i) - QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][acBasicAccount][eActual][i] == openingBalance); - QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][acBasicAccount][eActual][7] == openingBalance + MyMoneyMoney(6200000)); - QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][acBasicAccount][eActual][12] == MyMoneyMoney(18700000)); // value after t6 transaction + QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][i] == openingBalance); + QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][7] == openingBalance + MyMoneyMoney(6200000)); + QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][12] == MyMoneyMoney(18700000)); // value after t6 transaction filter.setDateFilter(QDate(2017, 1, 1), QDate(2017, 12, 31)); XMLandback(filter); PivotTable nt_opening2(filter); writeTabletoCSV(nt_opening2, "networth-opening-2.csv"); - QVERIFY(nt_opening2.m_grid["Asset"]["Basic Account"][acBasicAccount][eActual][0] == MyMoneyMoney(18700000)); // opening value is equall to the value after t6 transaction - QVERIFY(nt_opening2.m_grid["Asset"]["Basic Account"][acBasicAccount][eActual][12] == MyMoneyMoney(14800000)); + QVERIFY(nt_opening2.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][0] == MyMoneyMoney(18700000)); // opening value is equall to the value after t6 transaction + QVERIFY(nt_opening2.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][12] == MyMoneyMoney(14800000)); } void PivotTableTest::testSpendingEmpty() { // test a spending report with no entries MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); XMLandback(filter); PivotTable spending_f1(filter); QVERIFY(spending_f1.m_grid.m_total[eActual].m_total == moZero); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == moZero); } void PivotTableTest::testSingleTransaction() { // Test a single transaction TransactionHelper t(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.setName("Spending with Single Transaction.html"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoHTML(spending_f, "Spending with Single Transaction.html"); - QVERIFY(spending_f.m_grid["Expense"]["Solo"][acSolo][eActual][1] == moSolo); + QVERIFY(spending_f.m_grid["Expense"]["Solo"][ReportAccount(acSolo)][eActual][1] == moSolo); QVERIFY(spending_f.m_grid["Expense"]["Solo"].m_total[eActual][1] == moSolo); QVERIFY(spending_f.m_grid["Expense"]["Solo"].m_total[eActual][0] == moZero); QVERIFY(spending_f.m_grid.m_total[eActual][1] == (-moSolo)); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == (-moSolo)); filter.clearTransactionFilter(); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"].m_total[eActual][2] == (moCheckingOpen - moSolo)); } void PivotTableTest::testSubAccount() { // Test a sub-account with a value, under an account with a value TransactionHelper t1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.setDetailLevel(MyMoneyReport::eDetailAll); filter.setName("Spending with Sub-Account"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoHTML(spending_f, "Spending with Sub-Account.html"); - QVERIFY(spending_f.m_grid["Expense"]["Parent"][acParent][eActual][2] == moParent); - QVERIFY(spending_f.m_grid["Expense"]["Parent"][acChild][eActual][2] == moChild); + QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acParent)][eActual][2] == moParent); + QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acChild)][eActual][2] == moChild); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual][2] == moParent + moChild); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual][1] == moZero); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual].m_total == moParent + moChild); QVERIFY(spending_f.m_grid.m_total[eActual][2] == (-moParent - moChild)); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == (-moParent - moChild)); filter.clearTransactionFilter(); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.setName("Net Worth with Sub-Account"); XMLandback(filter); PivotTable networth_f(filter); writeTabletoHTML(networth_f, "Net Worth with Sub-Account.html"); QVERIFY(networth_f.m_grid["Liability"]["Credit Card"].m_total[eActual][3] == moParent + moChild - moCreditOpen); QVERIFY(networth_f.m_grid.m_total[eActual][4] == -moParent - moChild + moCreditOpen + moCheckingOpen); } void PivotTableTest::testFilterIEvsIE() { // Test that removing an income/spending account will remove the entry from an income/spending report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addCategory(acChild); filter.addCategory(acSolo); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual][2] == moChild); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][1] == moSolo); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo - moChild); } void PivotTableTest::testFilterALvsAL() { // Test that removing an asset/liability account will remove the entry from an asset/liability report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addAccount(acChecking); filter.addCategory(acChild); filter.addCategory(acSolo); XMLandback(filter); PivotTable networth_f(filter); QVERIFY(networth_f.m_grid.m_total[eActual][2] == -moSolo + moCheckingOpen); } void PivotTableTest::testFilterALvsIE() { // Test that removing an asset/liability account will remove the entry from an income/spending report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addAccount(acChecking); QVERIFY(file->transactionList(filter).count() == 1); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][2] == moZero); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][1] == moSolo); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo); } void PivotTableTest::testFilterAllvsIE() { // Test that removing an asset/liability account AND an income/expense // category will remove the entry from an income/spending report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addAccount(acCredit); filter.addCategory(acChild); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][1] == moZero); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][2] == moChild); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moChild); } void PivotTableTest::testFilterBasics() { // Test that the filters are operating the way that the reports expect them to TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); MyMoneyTransactionFilter filter; filter.clear(); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addCategory(acSolo); filter.setReportAllSplits(false); filter.setConsiderCategory(true); QVERIFY(file->transactionList(filter).count() == 1); filter.addCategory(acParent); QVERIFY(file->transactionList(filter).count() == 3); filter.addAccount(acChecking); QVERIFY(file->transactionList(filter).count() == 1); filter.clear(); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addCategory(acParent); filter.addAccount(acCredit); filter.setReportAllSplits(false); filter.setConsiderCategory(true); QVERIFY(file->transactionList(filter).count() == 2); } void PivotTableTest::testMultipleCurrencies() { MyMoneyMoney moCanOpening(0.0, 10); MyMoneyMoney moJpyOpening(0.0, 10); MyMoneyMoney moCanPrice(0.75, 100); MyMoneyMoney moJpyPrice(0.010, 1000); MyMoneyMoney moJpyPrice2(0.011, 1000); MyMoneyMoney moJpyPrice3(0.014, 1000); MyMoneyMoney moJpyPrice4(0.0395, 10000); MyMoneyMoney moCanTransaction(100.0, 10); MyMoneyMoney moJpyTransaction(100.0, 10); QString acCanChecking = makeAccount(QString("Canadian Checking"), eMyMoney::Account::Type::Checkings, moCanOpening, QDate(2003, 11, 15), acAsset, "CAD"); QString acJpyChecking = makeAccount(QString("Japanese Checking"), eMyMoney::Account::Type::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY"); QString acCanCash = makeAccount(QString("Canadian"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acForeign, "CAD"); QString acJpyCash = makeAccount(QString("Japanese"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acForeign, "JPY"); makePrice("CAD", QDate(2004, 1, 1), MyMoneyMoney(moCanPrice)); makePrice("JPY", QDate(2004, 1, 1), MyMoneyMoney(moJpyPrice)); makePrice("JPY", QDate(2004, 5, 1), MyMoneyMoney(moJpyPrice2)); makePrice("JPY", QDate(2004, 6, 30), MyMoneyMoney(moJpyPrice3)); makePrice("JPY", QDate(2004, 7, 15), MyMoneyMoney(moJpyPrice4)); TransactionHelper t1(QDate(2004, 2, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY"); TransactionHelper t2(QDate(2004, 3, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY"); TransactionHelper t3(QDate(2004, 4, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY"); TransactionHelper t4(QDate(2004, 2, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD"); TransactionHelper t5(QDate(2004, 3, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD"); TransactionHelper t6(QDate(2004, 4, 20), MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD"); #if 0 QFile g("multicurrencykmy.xml"); g.open(QIODevice::WriteOnly); MyMoneyStorageXML xml; IMyMoneyStorageFormat& interface = xml; interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); #endif MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setDetailLevel(MyMoneyReport::eDetailAll); filter.setConvertCurrency(true); filter.setName("Multiple Currency Spending Rerport (with currency conversion)"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoCSV(spending_f); // test single foreign currency - QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acCanCash][eActual][1] == (moCanTransaction*moCanPrice)); - QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acCanCash][eActual][2] == (moCanTransaction*moCanPrice)); - QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acCanCash][eActual][3] == (moCanTransaction*moCanPrice)); + QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][1] == (moCanTransaction*moCanPrice)); + QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][2] == (moCanTransaction*moCanPrice)); + QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][3] == (moCanTransaction*moCanPrice)); // test multiple foreign currencies under a common parent - QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acJpyCash][eActual][1] == (moJpyTransaction*moJpyPrice)); - QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acJpyCash][eActual][2] == (moJpyTransaction*moJpyPrice)); - QVERIFY(spending_f.m_grid["Expense"]["Foreign"][acJpyCash][eActual][3] == (moJpyTransaction*moJpyPrice)); + QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][1] == (moJpyTransaction*moJpyPrice)); + QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][2] == (moJpyTransaction*moJpyPrice)); + QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][3] == (moJpyTransaction*moJpyPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"].m_total[eActual][1] == (moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"].m_total[eActual].m_total == (moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice + moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice + moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice)); // Test the report type where we DO NOT convert the currency filter.setConvertCurrency(false); filter.setDetailLevel(MyMoneyReport::eDetailAll); filter.setName("Multiple Currency Spending Report (WITHOUT currency conversion)"); XMLandback(filter); PivotTable spending_fnc(filter); writeTabletoCSV(spending_fnc); - QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acCanCash][eActual][1] == (moCanTransaction)); - QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acCanCash][eActual][2] == (moCanTransaction)); - QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acCanCash][eActual][3] == (moCanTransaction)); - QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acJpyCash][eActual][1] == (moJpyTransaction)); - QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acJpyCash][eActual][2] == (moJpyTransaction)); - QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][acJpyCash][eActual][3] == (moJpyTransaction)); + QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][1] == (moCanTransaction)); + QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][2] == (moCanTransaction)); + QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][3] == (moCanTransaction)); + QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][1] == (moJpyTransaction)); + QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][2] == (moJpyTransaction)); + QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][3] == (moJpyTransaction)); filter.setConvertCurrency(true); filter.clearTransactionFilter(); filter.setName("Multiple currency net worth"); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); writeTabletoCSV(networth_f); // test single foreign currency - QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][1] == (moCanOpening*moCanPrice)); - QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][2] == ((moCanOpening - moCanTransaction)*moCanPrice)); - QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][3] == ((moCanOpening - moCanTransaction - moCanTransaction)*moCanPrice)); - QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][4] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice)); - QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][acCanChecking][eActual][12] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][1] == (moCanOpening*moCanPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][2] == ((moCanOpening - moCanTransaction)*moCanPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][3] == ((moCanOpening - moCanTransaction - moCanTransaction)*moCanPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][4] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][12] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice)); // test Stable currency price, fluctuating account balance - QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][1] == (moJpyOpening*moJpyPrice)); - QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][2] == ((moJpyOpening - moJpyTransaction)*moJpyPrice)); - QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][3] == ((moJpyOpening - moJpyTransaction - moJpyTransaction)*moJpyPrice)); - QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][4] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][1] == (moJpyOpening*moJpyPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][2] == ((moJpyOpening - moJpyTransaction)*moJpyPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][3] == ((moJpyOpening - moJpyTransaction - moJpyTransaction)*moJpyPrice)); + QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][4] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice)); // test Fluctuating currency price, stable account balance - QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][5] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice2)); - QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][6] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice3)); - QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][acJpyChecking][eActual][7] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice4)); + QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][5] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice2)); + QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][6] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice3)); + QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][7] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice4)); // test multiple currencies totalled up QVERIFY(networth_f.m_grid["Asset"].m_total[eActual][4] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice) + ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice)); QVERIFY(networth_f.m_grid["Asset"].m_total[eActual][5] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice) + ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice2) + moCheckingOpen); } void PivotTableTest::testAdvancedFilter() { // test more advanced filtering capabilities // amount { TransactionHelper t1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setAmountFilter(moChild, moChild); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moChild); } // payee (specific) { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moThomas, acCredit, acParent, QString(), "Thomas Baumgart"); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.addPayee(MyMoneyFile::instance()->payeeByName("Thomas Baumgart").id()); filter.setName("Spending with Payee Filter"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoHTML(spending_f, "Spending with Payee Filter.html"); - QVERIFY(spending_f.m_grid["Expense"]["Parent"][acParent][eActual][10] == moThomas); + QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acParent)][eActual][10] == moThomas); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moThomas); } // payee (no payee) { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moNoPayee, acCredit, acParent, QString(), QString()); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.addPayee(QString()); XMLandback(filter); PivotTable spending_f(filter); - QVERIFY(spending_f.m_grid["Expense"]["Parent"][acParent][eActual][10] == moNoPayee); + QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acParent)][eActual][10] == moNoPayee); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moNoPayee); } // text { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moThomas, acCredit, acParent, QString(), "Thomas Baumgart"); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setTextFilter(QRegExp("Thomas")); XMLandback(filter); PivotTable spending_f(filter); } // type (payment, deposit, transfer) { TransactionHelper t1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 2, 1), MyMoneySplit::ActionDeposit, -moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 1), MyMoneySplit::ActionTransfer, moChild, acCredit, acChecking); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.addType((int)eMyMoney::TransactionFilter::Type::Payments); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo); filter.clearTransactionFilter(); filter.addType((int)eMyMoney::TransactionFilter::Type::Deposits); XMLandback(filter); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == moParent1); filter.clearTransactionFilter(); filter.addType((int)eMyMoney::TransactionFilter::Type::Transfers); XMLandback(filter); PivotTable spending_f3(filter); QVERIFY(spending_f3.m_grid.m_total[eActual].m_total == moZero); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2004, 12, 31)); XMLandback(filter); PivotTable networth_f4(filter); QVERIFY(networth_f4.m_grid["Asset"].m_total[eActual][11] == moCheckingOpen + moChild); QVERIFY(networth_f4.m_grid["Liability"].m_total[eActual][11] == - moCreditOpen + moChild); QVERIFY(networth_f4.m_grid.m_total[eActual][9] == moCheckingOpen + moCreditOpen); QVERIFY(networth_f4.m_grid.m_total[eActual][10] == moCheckingOpen + moCreditOpen); } // state (reconciled, cleared, not) { TransactionHelper t1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); QList splits = t1.splits(); splits[0].setReconcileFlag(eMyMoney::Split::State::Cleared); splits[1].setReconcileFlag(eMyMoney::Split::State::Cleared); t1.modifySplit(splits[0]); t1.modifySplit(splits[1]); t1.update(); splits.clear(); splits = t2.splits(); splits[0].setReconcileFlag(eMyMoney::Split::State::Reconciled); splits[1].setReconcileFlag(eMyMoney::Split::State::Reconciled); t2.modifySplit(splits[0]); t2.modifySplit(splits[1]); t2.update(); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo); filter.addState((int)eMyMoney::TransactionFilter::State::Reconciled); XMLandback(filter); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == -moSolo - moParent1); filter.clearTransactionFilter(); filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); XMLandback(filter); PivotTable spending_f3(filter); QVERIFY(spending_f3.m_grid.m_total[eActual].m_total == -moChild - moParent2); } // number { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); QList splits = t1.splits(); splits[0].setNumber("1"); splits[1].setNumber("1"); t1.modifySplit(splits[0]); t1.modifySplit(splits[1]); t1.update(); splits.clear(); splits = t2.splits(); splits[0].setNumber("2"); splits[1].setNumber("2"); t2.modifySplit(splits[0]); t2.modifySplit(splits[1]); t2.update(); splits.clear(); splits = t3.splits(); splits[0].setNumber("3"); splits[1].setNumber("3"); t3.modifySplit(splits[0]); t3.modifySplit(splits[1]); t3.update(); splits.clear(); splits = t2.splits(); splits[0].setNumber("4"); splits[1].setNumber("4"); t4.modifySplit(splits[0]); t4.modifySplit(splits[1]); t4.update(); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setNumberFilter("1", "3"); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo - moParent1 - moParent2); } // blank dates { TransactionHelper t1y1(QDate(2003, 10, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y1(QDate(2003, 11, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y1(QDate(2003, 12, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t1y2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t1y3(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y3(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y3(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(), QDate(2004, 7, 1)); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo - moParent1 - moParent2 - moSolo - moParent1 - moParent2); filter.clearTransactionFilter(); XMLandback(filter); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == -moSolo - moParent1 - moParent2 - moSolo - moParent1 - moParent2 - moSolo - moParent1 - moParent2); } } void PivotTableTest::testColumnType() { // test column type values of other than 'month' TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2003, 12, 31), QDate(2005, 12, 31)); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(MyMoneyReport::eBiMonths); XMLandback(filter); PivotTable spending_b(filter); QVERIFY(spending_b.m_grid.m_total[eActual][0] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][1] == -moParent1 - moSolo); QVERIFY(spending_b.m_grid.m_total[eActual][2] == -moParent2 - moSolo); QVERIFY(spending_b.m_grid.m_total[eActual][3] == -moParent); QVERIFY(spending_b.m_grid.m_total[eActual][4] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][5] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][6] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][7] == -moSolo); QVERIFY(spending_b.m_grid.m_total[eActual][8] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][9] == -moParent1); QVERIFY(spending_b.m_grid.m_total[eActual][10] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][11] == -moParent2); QVERIFY(spending_b.m_grid.m_total[eActual][12] == moZero); filter.setColumnType(MyMoneyReport::eQuarters); XMLandback(filter); PivotTable spending_q(filter); QVERIFY(spending_q.m_grid.m_total[eActual][0] == moZero); QVERIFY(spending_q.m_grid.m_total[eActual][1] == -moSolo - moParent); QVERIFY(spending_q.m_grid.m_total[eActual][2] == -moSolo - moParent); QVERIFY(spending_q.m_grid.m_total[eActual][3] == moZero); QVERIFY(spending_q.m_grid.m_total[eActual][4] == moZero); QVERIFY(spending_q.m_grid.m_total[eActual][5] == -moSolo); QVERIFY(spending_q.m_grid.m_total[eActual][6] == -moParent1); QVERIFY(spending_q.m_grid.m_total[eActual][7] == -moParent2); QVERIFY(spending_q.m_grid.m_total[eActual][8] == moZero); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setName("Net Worth by Quarter"); XMLandback(filter); PivotTable networth_q(filter); writeTabletoHTML(networth_q, "Net Worth by Quarter.html"); QVERIFY(networth_q.m_grid.m_total[eActual][1] == moZero); QVERIFY(networth_q.m_grid.m_total[eActual][2] == -moSolo - moParent); QVERIFY(networth_q.m_grid.m_total[eActual][3] == -moSolo - moParent - moSolo - moParent + moCheckingOpen); QVERIFY(networth_q.m_grid.m_total[eActual][4] == -moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][5] == -moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][6] == -moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][7] == -moParent1 - moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][8] == -moParent2 - moParent1 - moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][9] == -moParent2 - moParent1 - moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(MyMoneyReport::eYears); XMLandback(filter); PivotTable spending_y(filter); QVERIFY(spending_y.m_grid.m_total[eActual][0] == moZero); QVERIFY(spending_y.m_grid.m_total[eActual][1] == -moSolo - moParent - moSolo - moParent); QVERIFY(spending_y.m_grid.m_total[eActual][2] == -moSolo - moParent); QVERIFY(spending_y.m_grid.m_total[eActual].m_total == -moSolo - moParent - moSolo - moParent - moSolo - moParent); filter.setRowType(MyMoneyReport::eAssetLiability); XMLandback(filter); PivotTable networth_y(filter); QVERIFY(networth_y.m_grid.m_total[eActual][1] == moZero); QVERIFY(networth_y.m_grid.m_total[eActual][2] == -moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_y.m_grid.m_total[eActual][3] == -moSolo - moParent - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); // Test days-based reports TransactionHelper t1d1(QDate(2004, 7, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2d1(QDate(2004, 7, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3d1(QDate(2004, 7, 4), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t1d2(QDate(2004, 7, 14), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2d2(QDate(2004, 7, 15), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3d2(QDate(2004, 7, 20), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t1d3(QDate(2004, 8, 2), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2d3(QDate(2004, 8, 3), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3d3(QDate(2004, 8, 4), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); filter.setDateFilter(QDate(2004, 7, 2), QDate(2004, 7, 14)); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(MyMoneyReport::eMonths); filter.setColumnsAreDays(true); filter.setName("Spending by Days"); XMLandback(filter); PivotTable spending_days(filter); writeTabletoHTML(spending_days, "Spending by Days.html"); QVERIFY(spending_days.m_grid.m_total[eActual][2] == -moParent2); QVERIFY(spending_days.m_grid.m_total[eActual][12] == -moSolo); QVERIFY(spending_days.m_grid.m_total[eActual].m_total == -moSolo - moParent2); // set the first day of the week to 1 QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom)); filter.setDateFilter(QDate(2004, 7, 2), QDate(2004, 8, 1)); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(static_cast(7)); filter.setColumnsAreDays(true); filter.setName("Spending by Weeks"); XMLandback(filter); PivotTable spending_weeks(filter); writeTabletoHTML(spending_weeks, "Spending by Weeks.html"); // restore the locale QLocale::setDefault(QLocale::system()); QVERIFY(spending_weeks.m_grid.m_total[eActual][0] == -moParent2); QVERIFY(spending_weeks.m_grid.m_total[eActual][1] == moZero); QVERIFY(spending_weeks.m_grid.m_total[eActual][2] == -moSolo - moParent1); QVERIFY(spending_weeks.m_grid.m_total[eActual][3] == -moParent2); QVERIFY(spending_weeks.m_grid.m_total[eActual][4] == moZero); QVERIFY(spending_weeks.m_grid.m_total[eActual].m_total == -moSolo - moParent - moParent2); } void PivotTableTest::testInvestment() { try { // Equities eqStock1 = makeEquity("Stock1", "STK1"); eqStock2 = makeEquity("Stock2", "STK2"); // Accounts acInvestment = makeAccount("Investment", eMyMoney::Account::Type::Investment, moZero, QDate(2004, 1, 1), acAsset); acStock1 = makeAccount("Stock 1", eMyMoney::Account::Type::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1); acStock2 = makeAccount("Stock 2", eMyMoney::Account::Type::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2); acDividends = makeAccount("Dividends", eMyMoney::Account::Type::Income, moZero, QDate(2004, 1, 1), acIncome); // Transactions // Date Action Shares Price Stock Asset Income InvTransactionHelper s1b1(QDate(2004, 2, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1b2(QDate(2004, 3, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(110.00), acStock1, acChecking, QString()); InvTransactionHelper s1s1(QDate(2004, 4, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(120.00), acStock1, acChecking, QString()); InvTransactionHelper s1s2(QDate(2004, 5, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1r1(QDate(2004, 6, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(100.00), acStock1, QString(), acDividends); InvTransactionHelper s1r2(QDate(2004, 7, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(80.00), acStock1, QString(), acDividends); InvTransactionHelper s1c1(QDate(2004, 8, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(100.00), acStock1, acChecking, acDividends); InvTransactionHelper s1c2(QDate(2004, 9, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(120.00), acStock1, acChecking, acDividends); makeEquityPrice(eqStock1, QDate(2004, 10, 1), MyMoneyMoney(100.00)); // // Net Worth Report (with investments) // MyMoneyReport networth_r; networth_r.setRowType(MyMoneyReport::eAssetLiability); networth_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 12, 31).addDays(-1)); XMLandback(networth_r); PivotTable networth(networth_r); networth.dump("networth_i.html"); QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][1] == moZero); // 1000 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][2] == MyMoneyMoney(100000.0)); // 2000 shares @ $110.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][3] == MyMoneyMoney(220000.0)); // 1800 shares @ $120.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][4] == MyMoneyMoney(216000.0)); // 1600 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][5] == MyMoneyMoney(160000.0)); // 1650 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][6] == MyMoneyMoney(165000.0)); // 1700 shares @ $ 80.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][7] == MyMoneyMoney(136000.0)); // 1700 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][8] == MyMoneyMoney(170000.0)); // 1700 shares @ $120.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][9] == MyMoneyMoney(204000.0)); // 1700 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][10] == MyMoneyMoney(170000.0)); #if 0 // Dump file & reports QFile g("investmentkmy.xml"); g.open(QIODevice::WriteOnly); MyMoneyStorageXML xml; IMyMoneyStorageFormat& interface = xml; interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); invtran.dump("invtran.html", "%1"); invhold.dump("invhold.html", "%1"); #endif } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void PivotTableTest::testBudget() { // 1. Budget on A, transactions on A { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acSolo, false, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // 2. Budget on B, not applying to sub accounts, transactions on B and B:1 { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acParent, false, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // - Both B and B:1 totals should show up // - B actuals compare against B budget // - B:1 actuals compare against 0 // 3. Budget on C, applying to sub accounts, transactions on C and C:1 and C:1:a { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acParent, true, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop , "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // - Only C totals show up, not C:1 or C:1:a totals // - C + C:1 totals compare against C budget // 4. Budget on D, not applying to sub accounts, budget on D:1 not applying, budget on D:2 applying. Transactions on D, D:1, D:2, D:2:a, D:2:b { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acParent, false, MyMoneyMoney(100.0)); budget += BudgetEntryHelper(QDate(2006, 1, 1), acChild, false, MyMoneyMoney(100.0)); budget += BudgetEntryHelper(QDate(2006, 1, 1), acSecondChild, true, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // - Totals for D, D:1, D:2 show up. D:2:a and D:2:b do not // - D actuals (only) compare against D budget // - Ditto for D:1 // - D:2 acutals and children compare against D:2 budget // 5. Budget on E, no transactions on E { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acSolo, false, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } } void PivotTableTest::testHtmlEncoding() { MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); QByteArray encoding = QTextCodec::codecForLocale()->name(); QString html = networth_f.renderReport(QLatin1String("html"), encoding, filter.name(), false); QRegExp rx(QString::fromLatin1("**")); rx.setPatternSyntax(QRegExp::Wildcard); rx.setCaseSensitivity(Qt::CaseInsensitive); QVERIFY(rx.exactMatch(html)); } diff --git a/kmymoney/views/kaccountsview_p.h b/kmymoney/views/kaccountsview_p.h index 43da8787b..2ba41826e 100644 --- a/kmymoney/views/kaccountsview_p.h +++ b/kmymoney/views/kaccountsview_p.h @@ -1,79 +1,78 @@ /*************************************************************************** kaccountsview.cpp ------------------- copyright : (C) 2007 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KACCOUNTSVIEW_P_H #define KACCOUNTSVIEW_P_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_kaccountsview.h" #include "kmymoneyaccountsviewbase_p.h" #include "accountsviewproxymodel.h" #include "kmymoneyglobalsettings.h" #include "icons.h" using namespace Icons; namespace Ui { class KAccountsView; } class KAccountsViewPrivate : public KMyMoneyAccountsViewBasePrivate { - Q_DECLARE_PUBLIC(KAccountsView) + Q_DECLARE_PUBLIC(KAccountsView) public: - - KAccountsViewPrivate(KAccountsView *qq) : - q_ptr(qq), - ui(new Ui::KAccountsView), - m_haveUnusedCategories(false) - { - } - - ~KAccountsViewPrivate() - { - } - - void init() - { - Q_Q(KAccountsView); - ui->setupUi(q); - m_accountTree = &ui->m_accountTree; - - // setup icons for collapse and expand button - ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); - ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); - - m_proxyModel = ui->m_accountTree->init(View::Accounts); - q->connect(m_proxyModel, &AccountsProxyModel::unusedIncomeExpenseAccountHidden, q, &KAccountsView::slotUnusedIncomeExpenseAccountHidden); - q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); - } - - KAccountsView *q_ptr; - Ui::KAccountsView *ui; - bool m_haveUnusedCategories; + explicit KAccountsViewPrivate(KAccountsView *qq) : + q_ptr(qq), + ui(new Ui::KAccountsView), + m_haveUnusedCategories(false) + { + } + + ~KAccountsViewPrivate() + { + } + + void init() + { + Q_Q(KAccountsView); + ui->setupUi(q); + m_accountTree = &ui->m_accountTree; + + // setup icons for collapse and expand button + ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); + ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); + + m_proxyModel = ui->m_accountTree->init(View::Accounts); + q->connect(m_proxyModel, &AccountsProxyModel::unusedIncomeExpenseAccountHidden, q, &KAccountsView::slotUnusedIncomeExpenseAccountHidden); + q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); + } + + KAccountsView *q_ptr; + Ui::KAccountsView *ui; + bool m_haveUnusedCategories; }; #endif diff --git a/kmymoney/views/kbudgetview_p.h b/kmymoney/views/kbudgetview_p.h index 276ed0f83..304836523 100644 --- a/kmymoney/views/kbudgetview_p.h +++ b/kmymoney/views/kbudgetview_p.h @@ -1,124 +1,123 @@ /*************************************************************************** kbudgetview.cpp --------------- begin : Thu Jan 10 2006 copyright : (C) 2006 by Darren Gould email : darren_gould@gmx.de Alvaro Soliverez (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KBUDGETVIEW_P_H #define KBUDGETVIEW_P_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kbudgetview.h" #include "kmymoneyaccountsviewbase_p.h" #include "budgetviewproxymodel.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "icons.h" using namespace Icons; namespace Ui { - class KBudgetView; + class KBudgetView; } class KBudgetViewPrivate : public KMyMoneyAccountsViewBasePrivate { - Q_DECLARE_PUBLIC(KBudgetView) + Q_DECLARE_PUBLIC(KBudgetView) public: + explicit KBudgetViewPrivate(KBudgetView *qq) : + q_ptr(qq), + ui(new Ui::KBudgetView) + { + } - KBudgetViewPrivate(KBudgetView *qq) : - q_ptr(qq), - ui(new Ui::KBudgetView) - { - } + ~KBudgetViewPrivate() + { + } - ~KBudgetViewPrivate() - { - } + void init() + { + Q_Q(KBudgetView); + ui->setupUi(q); + m_accountTree = &ui->m_accountTree; - void init() - { - Q_Q(KBudgetView); - ui->setupUi(q); - m_accountTree = &ui->m_accountTree; + ui->m_budgetList->setContextMenuPolicy(Qt::CustomContextMenu); + ui->m_newButton->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); + ui->m_renameButton->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); + ui->m_deleteButton->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::EditDelete])); + ui->m_updateButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentSave])); + ui->m_resetButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditUndo])); + ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); + ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); - ui->m_budgetList->setContextMenuPolicy(Qt::CustomContextMenu); - ui->m_newButton->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - ui->m_renameButton->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - ui->m_deleteButton->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::EditDelete])); - ui->m_updateButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentSave])); - ui->m_resetButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditUndo])); - ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); - ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); + m_budgetProxyModel = qobject_cast(ui->m_accountTree->init(View::Budget)); + m_proxyModel = m_budgetProxyModel; - m_budgetProxyModel = qobject_cast(ui->m_accountTree->init(View::Budget)); - m_proxyModel = m_budgetProxyModel; + q->connect(m_budgetProxyModel, &BudgetViewProxyModel::balanceChanged, q, &KBudgetView::slotBudgetBalanceChanged); - q->connect(m_budgetProxyModel, &BudgetViewProxyModel::balanceChanged, q, &KBudgetView::slotBudgetBalanceChanged); + q->connect(ui->m_accountTree, SIGNAL(selectObject(MyMoneyObject)), q, SLOT(slotSelectAccount(MyMoneyObject))); - q->connect(ui->m_accountTree, SIGNAL(selectObject(MyMoneyObject)), q, SLOT(slotSelectAccount(MyMoneyObject))); + q->connect(ui->m_budgetList, &QWidget::customContextMenuRequested, + q, &KBudgetView::slotOpenContextMenu); + q->connect(ui->m_budgetList->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KBudgetView::slotSelectBudget); + q->connect(ui->m_budgetList, &QTreeWidget::itemChanged, q, &KBudgetView::slotItemChanged); - q->connect(ui->m_budgetList, &QWidget::customContextMenuRequested, - q, &KBudgetView::slotOpenContextMenu); - q->connect(ui->m_budgetList->selectionModel(), &QItemSelectionModel::selectionChanged, q, &KBudgetView::slotSelectBudget); - q->connect(ui->m_budgetList, &QTreeWidget::itemChanged, q, &KBudgetView::slotItemChanged); + q->connect(ui->m_cbBudgetSubaccounts, &QAbstractButton::clicked, q, &KBudgetView::cb_includesSubaccounts_clicked); - q->connect(ui->m_cbBudgetSubaccounts, &QAbstractButton::clicked, q, &KBudgetView::cb_includesSubaccounts_clicked); + // connect the buttons to the actions. Make sure the enabled state + // of the actions is reflected by the buttons + q->connect(ui->m_renameButton, &QAbstractButton::clicked, kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetRename]), &QAction::trigger); + q->connect(ui->m_deleteButton, &QAbstractButton::clicked, kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetDelete]), &QAction::trigger); - // connect the buttons to the actions. Make sure the enabled state - // of the actions is reflected by the buttons - q->connect(ui->m_renameButton, &QAbstractButton::clicked, kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetRename]), &QAction::trigger); - q->connect(ui->m_deleteButton, &QAbstractButton::clicked, kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetDelete]), &QAction::trigger); + q->connect(ui->m_budgetValue, &KBudgetValues::valuesChanged, q, &KBudgetView::slotBudgetedAmountChanged); - q->connect(ui->m_budgetValue, &KBudgetValues::valuesChanged, q, &KBudgetView::slotBudgetedAmountChanged); + q->connect(ui->m_newButton, &QAbstractButton::clicked, q, &KBudgetView::slotNewBudget); + q->connect(ui->m_updateButton, &QAbstractButton::clicked, q, &KBudgetView::slotUpdateBudget); + q->connect(ui->m_resetButton, &QAbstractButton::clicked, q, &KBudgetView::slotResetBudget); - q->connect(ui->m_newButton, &QAbstractButton::clicked, q, &KBudgetView::slotNewBudget); - q->connect(ui->m_updateButton, &QAbstractButton::clicked, q, &KBudgetView::slotUpdateBudget); - q->connect(ui->m_resetButton, &QAbstractButton::clicked, q, &KBudgetView::slotResetBudget); + q->connect(ui->m_hideUnusedButton, &QAbstractButton::toggled, q, &KBudgetView::slotHideUnused); - q->connect(ui->m_hideUnusedButton, &QAbstractButton::toggled, q, &KBudgetView::slotHideUnused); + q->connect(ui->m_searchWidget, SIGNAL(textChanged(QString)), m_budgetProxyModel, SLOT(setFilterFixedString(QString))); - q->connect(ui->m_searchWidget, SIGNAL(textChanged(QString)), m_budgetProxyModel, SLOT(setFilterFixedString(QString))); + // setup initial state + ui->m_newButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetNew])->isEnabled()); + ui->m_renameButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetRename])->isEnabled()); + ui->m_deleteButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetDelete])->isEnabled()); - // setup initial state - ui->m_newButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetNew])->isEnabled()); - ui->m_renameButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetRename])->isEnabled()); - ui->m_deleteButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetDelete])->isEnabled()); + auto grp = KSharedConfig::openConfig()->group("Last Use Settings"); + ui->m_splitter->restoreState(grp.readEntry("KBudgetViewSplitterSize", QByteArray())); + ui->m_splitter->setChildrenCollapsible(false); + } - auto grp = KSharedConfig::openConfig()->group("Last Use Settings"); - ui->m_splitter->restoreState(grp.readEntry("KBudgetViewSplitterSize", QByteArray())); - ui->m_splitter->setChildrenCollapsible(false); - } - - KBudgetView *q_ptr; - Ui::KBudgetView *ui; - BudgetViewProxyModel *m_budgetProxyModel; + KBudgetView *q_ptr; + Ui::KBudgetView *ui; + BudgetViewProxyModel *m_budgetProxyModel; }; #endif diff --git a/kmymoney/views/kcategoriesview_p.h b/kmymoney/views/kcategoriesview_p.h index b50eb794b..0134f9871 100644 --- a/kmymoney/views/kcategoriesview_p.h +++ b/kmymoney/views/kcategoriesview_p.h @@ -1,87 +1,86 @@ /*************************************************************************** kcategoriesview.cpp - description ------------------- begin : Sun Jan 20 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KCATEGORIESVIEW_P_H #define KCATEGORIESVIEW_P_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_kcategoriesview.h" #include "kmymoneyaccountsviewbase_p.h" #include "accountsviewproxymodel.h" #include "kmymoneyglobalsettings.h" #include "icons.h" using namespace Icons; namespace Ui { class KCategoriesView; } class KCategoriesViewPrivate : public KMyMoneyAccountsViewBasePrivate { - Q_DECLARE_PUBLIC(KCategoriesView) + Q_DECLARE_PUBLIC(KCategoriesView) public: + explicit KCategoriesViewPrivate(KCategoriesView *qq) : + q_ptr(qq), + ui(new Ui::KCategoriesView), + m_haveUnusedCategories(false) + { + } - KCategoriesViewPrivate(KCategoriesView *qq) : - q_ptr(qq), - ui(new Ui::KCategoriesView), - m_haveUnusedCategories(false) - { - } + ~KCategoriesViewPrivate() + { + } - ~KCategoriesViewPrivate() - { - } + void init() + { + Q_Q(KCategoriesView); + ui->setupUi(q); + m_accountTree = &ui->m_accountTree; - void init() - { - Q_Q(KCategoriesView); - ui->setupUi(q); - m_accountTree = &ui->m_accountTree; + // setup icons for collapse and expand button + ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); + ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); - // setup icons for collapse and expand button - ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); - ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); + m_proxyModel = ui->m_accountTree->init(View::Categories); - m_proxyModel = ui->m_accountTree->init(View::Categories); + q->connect(m_proxyModel, &AccountsProxyModel::unusedIncomeExpenseAccountHidden, q, &KCategoriesView::slotUnusedIncomeExpenseAccountHidden); + q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); - q->connect(m_proxyModel, &AccountsProxyModel::unusedIncomeExpenseAccountHidden, q, &KCategoriesView::slotUnusedIncomeExpenseAccountHidden); - q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); + } - } - - KCategoriesView *q_ptr; - Ui::KCategoriesView *ui; - bool m_haveUnusedCategories; + KCategoriesView *q_ptr; + Ui::KCategoriesView *ui; + bool m_haveUnusedCategories; }; #endif diff --git a/kmymoney/views/kforecastview.h b/kmymoney/views/kforecastview.h index 96e430170..07e392705 100644 --- a/kmymoney/views/kforecastview.h +++ b/kmymoney/views/kforecastview.h @@ -1,186 +1,186 @@ /*************************************************************************** kforecastview.h ------------------- copyright : (C) 2007 by Alvaro Soliverez email : asoliverez@gmail.com ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KFORECASTVIEW_H #define KFORECASTVIEW_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneyforecast.h" #include "mymoneyprice.h" #include "ui_kforecastviewdecl.h" namespace reports { class KReportChartView; } class FixedColumnTreeView; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyForecast; class MyMoneyPrice; /** * @author Alvaro Soliverez * * This class implements the forecast 'view'. */ class KForecastView : public QWidget, private Ui::KForecastViewDecl { Q_OBJECT public: enum EForecastViewType { eSummary = 0, eDetailed, eAdvanced, eBudget, eUndefined }; - KForecastView(QWidget *parent = 0); + explicit KForecastView(QWidget *parent = 0); virtual ~KForecastView(); void setDefaultFocus(); void showEvent(QShowEvent* event); public slots: void slotLoadForecast(); void slotManualForecast(); protected: typedef enum { SummaryView = 0, ListView, AdvancedView, BudgetView, ChartView, // insert new values above this line MaxViewTabs } ForecastViewTab; enum ForecastViewRoles { ForecastRole = Qt::UserRole, /**< The forecast is held in this role.*/ AccountRole = Qt::UserRole + 1, /**< The MyMoneyAccount is stored in this role in column 0.*/ AmountRole = Qt::UserRole + 2, /**< The amount.*/ ValueRole = Qt::UserRole + 3, /**< The value.*/ }; QMap m_nameIdx; /** * This method loads the forecast view. */ void loadForecast(ForecastViewTab tab); /** * This method loads the detailed view */ void loadListView(); /** * This method loads the summary view */ void loadSummaryView(); /** * This method loads the advanced view */ void loadAdvancedView(); /** * This method loads the budget view */ void loadBudgetView(); /** * This method loads the budget view */ void loadChartView(); /** * This method loads the settings from user configuration */ void loadForecastSettings(); protected slots: void slotTabChanged(int index); /** * Get the list of prices for an account * This is used later to create an instance of KMyMoneyAccountTreeForecastItem * */ QList getAccountPrices(const MyMoneyAccount& acc); private slots: void itemExpanded(QTreeWidgetItem *item); void itemCollapsed(QTreeWidgetItem *item); signals: /** * This signal is emitted whenever the view is about to be shown. */ void aboutToShow(); private: void addAssetLiabilityRows(const MyMoneyForecast& forecast); void addIncomeExpenseRows(const MyMoneyForecast& forecast); void addTotalRow(QTreeWidget* forecastList, const MyMoneyForecast& forecast); bool includeAccount(MyMoneyForecast& forecast, const MyMoneyAccount& acc); void loadAccounts(MyMoneyForecast& forecast, const MyMoneyAccount& account, QTreeWidgetItem* parentItem, int forecastType); void adjustHeadersAndResizeToContents(QTreeWidget *widget); void updateSummary(QTreeWidgetItem *item); void updateDetailed(QTreeWidgetItem *item); void updateBudget(QTreeWidgetItem *item); /** * Sets the whole item to be shown with negative colors */ void setNegative(QTreeWidgetItem *item, bool isNegative); void showAmount(QTreeWidgetItem *item, int column, const MyMoneyMoney &amount, const MyMoneySecurity &security); void adjustParentValue(QTreeWidgetItem *item, int column, const MyMoneyMoney& value); void setValue(QTreeWidgetItem *item, int column, const MyMoneyMoney &amount, const QDate &forecastDate); void setAmount(QTreeWidgetItem *item, int column, const MyMoneyMoney &amount); /** Initializes page and sets its load status to initialized */ void init(); bool m_needReload[MaxViewTabs]; /** * This member holds the load state of page */ bool m_needLoad; QTreeWidgetItem* m_totalItem; QTreeWidgetItem* m_assetItem; QTreeWidgetItem* m_liabilityItem; QTreeWidgetItem* m_incomeItem; QTreeWidgetItem* m_expenseItem; QLayout* m_chartLayout; reports::KReportChartView* m_forecastChart; QScopedPointer m_fixedColumnView; }; #endif diff --git a/kmymoney/views/kinstitutionsview.h b/kmymoney/views/kinstitutionsview.h index 8ccdaa34b..680e60505 100644 --- a/kmymoney/views/kinstitutionsview.h +++ b/kmymoney/views/kinstitutionsview.h @@ -1,65 +1,65 @@ /*************************************************************************** kinstitutionssview.h ------------------- copyright : (C) 2007 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KINSTITUTIONSVIEW_H #define KINSTITUTIONSVIEW_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyaccountsviewbase.h" namespace Ui { class KInstitutionsView; } /** * @author Thomas Baumgart */ /** * This class implements the institutions hierarchical 'view'. */ class MyMoneyMoney; class KInstitutionsViewPrivate; class KInstitutionsView : public KMyMoneyAccountsViewBase { Q_OBJECT public: - KInstitutionsView(QWidget *parent = nullptr); + explicit KInstitutionsView(QWidget *parent = nullptr); ~KInstitutionsView(); void setDefaultFocus() override; void refresh() override; public slots: void slotNetWorthChanged(const MyMoneyMoney &); protected: KInstitutionsView(KInstitutionsViewPrivate &dd, QWidget *parent); virtual void showEvent(QShowEvent * event) override; private: Q_DECLARE_PRIVATE(KInstitutionsView) }; #endif diff --git a/kmymoney/views/kinstitutionsview_p.h b/kmymoney/views/kinstitutionsview_p.h index fa3b5a792..771fdae0b 100644 --- a/kmymoney/views/kinstitutionsview_p.h +++ b/kmymoney/views/kinstitutionsview_p.h @@ -1,76 +1,75 @@ /*************************************************************************** kinstitutionsview_p.h ------------------- copyright : (C) 2007 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KINSTITUTIONSVIEW_P_H #define KINSTITUTIONSVIEW_P_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_kinstitutionsview.h" #include "kmymoneyaccountsviewbase_p.h" #include "accountsviewproxymodel.h" #include "kmymoneyglobalsettings.h" #include "icons.h" using namespace Icons; namespace Ui { - class KInstitutionsView; + class KInstitutionsView; } class KInstitutionsViewPrivate : public KMyMoneyAccountsViewBasePrivate { - Q_DECLARE_PUBLIC(KInstitutionsView) + Q_DECLARE_PUBLIC(KInstitutionsView) public: - - KInstitutionsViewPrivate(KInstitutionsView *qq) : - q_ptr(qq), - ui(new Ui::KInstitutionsView) - { - } - - ~KInstitutionsViewPrivate() - { - } - - void init() - { - Q_Q(KInstitutionsView); - ui->setupUi(q); - m_accountTree = &ui->m_accountTree; - - // setup icons for collapse and expand button - ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); - ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); - - // the proxy filter model - m_proxyModel = ui->m_accountTree->init(View::Institutions); - q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); - } - - KInstitutionsView *q_ptr; - Ui::KInstitutionsView *ui; + explicit KInstitutionsViewPrivate(KInstitutionsView *qq) : + q_ptr(qq), + ui(new Ui::KInstitutionsView) + { + } + + ~KInstitutionsViewPrivate() + { + } + + void init() + { + Q_Q(KInstitutionsView); + ui->setupUi(q); + m_accountTree = &ui->m_accountTree; + + // setup icons for collapse and expand button + ui->m_collapseButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListCollapse])); + ui->m_expandButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListExpand])); + + // the proxy filter model + m_proxyModel = ui->m_accountTree->init(View::Institutions); + q->connect(ui->m_searchWidget, &QLineEdit::textChanged, m_proxyModel, &QSortFilterProxyModel::setFilterFixedString); + } + + KInstitutionsView *q_ptr; + Ui::KInstitutionsView *ui; }; #endif diff --git a/kmymoney/views/kmymoneywebpage.h b/kmymoney/views/kmymoneywebpage.h index 34cfa383a..1eb94fdf1 100644 --- a/kmymoney/views/kmymoneywebpage.h +++ b/kmymoney/views/kmymoneywebpage.h @@ -1,61 +1,61 @@ /*************************************************************************** kmymoneywebpage.h ------------------- copyright : (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KMYMONEYWEBPAGE_H #define KMYMONEYWEBPAGE_H #include // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #ifdef ENABLE_WEBENGINE #include #else #include #endif // ---------------------------------------------------------------------------- // Project Includes #ifdef ENABLE_WEBENGINE class MyQWebEnginePage : public QWebEnginePage #else class MyQWebEnginePage : public KWebPage #endif { Q_OBJECT public: #ifdef ENABLE_WEBENGINE - MyQWebEnginePage(QObject* parent = nullptr) : QWebEnginePage(parent){} + explicit MyQWebEnginePage(QObject* parent = nullptr) : QWebEnginePage(parent){} #else - MyQWebEnginePage(QObject* parent = nullptr) : KWebPage(parent){} + explicit MyQWebEnginePage(QObject* parent = nullptr) : KWebPage(parent){} #endif protected: #ifdef ENABLE_WEBENGINE bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool); #else bool acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, NavigationType type); #endif }; #endif diff --git a/kmymoney/views/kpayeeidentifierview.h b/kmymoney/views/kpayeeidentifierview.h index 6518f5886..d9d675c98 100644 --- a/kmymoney/views/kpayeeidentifierview.h +++ b/kmymoney/views/kpayeeidentifierview.h @@ -1,62 +1,62 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 KPAYEEIDENTIFIERVIEW_H #define KPAYEEIDENTIFIERVIEW_H #include #include "mymoney/mymoneypayeeidentifiercontainer.h" #include "widgets/styleditemdelegateforwarder.h" namespace Ui { class KPayeeIdentifierView; }; class KPayeeIdentifierView : public QWidget { Q_OBJECT public: - KPayeeIdentifierView(QWidget* parent); + explicit KPayeeIdentifierView(QWidget* parent); ~KPayeeIdentifierView(); QList identifiers() const; signals: void dataChanged(); public slots: void setSource(MyMoneyPayeeIdentifierContainer data); private slots: void removeSelected(); private: Ui::KPayeeIdentifierView* ui; }; class payeeIdentifierDelegate : public StyledItemDelegateForwarder { Q_OBJECT public: - payeeIdentifierDelegate(QObject* parent = 0); + explicit payeeIdentifierDelegate(QObject* parent = 0); virtual QAbstractItemDelegate* getItemDelegate(const QModelIndex& index) const; }; #endif // KPAYEEIDENTIFIERVIEW_H diff --git a/kmymoney/views/kpayeesview.h b/kmymoney/views/kpayeesview.h index 4aa9d2d2b..35704d53a 100644 --- a/kmymoney/views/kpayeesview.h +++ b/kmymoney/views/kpayeesview.h @@ -1,245 +1,245 @@ /*************************************************************************** kpayeesview.h ------------- begin : Thu Jan 24 2002 copyright : (C) 2000-2002 by Michael Edwardes 2005 by Andrea Nicolai 2006 by Thomas Baumgart Javier Campos Morales Felix Rodriguez John C Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KPAYEESVIEW_H #define KPAYEESVIEW_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_kpayeesviewdecl.h" #include "mymoneypayee.h" /** * @author Michael Edwardes, Thomas Baumgart */ /** * This class represents an item in the payees list view. */ struct ContactData; class MyMoneyContact; class KListWidgetSearchLine; class KPayeeListItem : public QListWidgetItem { public: /** * Constructor to be used to construct a payee entry object. * * @param parent pointer to the QListWidget object this entry should be * added to. * @param payee const reference to MyMoneyPayee for which * the QListWidget entry is constructed */ KPayeeListItem(QListWidget *parent, const MyMoneyPayee& payee); ~KPayeeListItem(); const MyMoneyPayee& payee() const { return m_payee; }; private: MyMoneyPayee m_payee; }; class KPayeesView : public QWidget, private Ui::KPayeesViewDecl { Q_OBJECT public: - KPayeesView(QWidget *parent = 0); + explicit KPayeesView(QWidget *parent = 0); ~KPayeesView(); void setDefaultFocus(); void showEvent(QShowEvent* event); enum filterTypeE { eAllPayees = 0, eReferencedPayees = 1, eUnusedPayees = 2 }; public slots: void slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId = QString(), const QString& transactionId = QString()); void slotLoadPayees(); void slotStartRename(QListWidgetItem*); void slotHelp(); protected: void loadPayees(); void selectedPayees(QList& payeesList) const; void ensurePayeeVisible(const QString& id); void clearItemData(); protected slots: /** * This method loads the m_transactionList, clears * the m_TransactionPtrVector and rebuilds and sorts * it according to the current settings. Then it * loads the m_transactionView with the transaction data. */ void showTransactions(); /** * This slot is called whenever the selection in m_payeesList * is about to change. */ void slotSelectPayee(QListWidgetItem* cur, QListWidgetItem* prev); /** * This slot is called whenever the selection in m_payeesList * has been changed. */ void slotSelectPayee(); /** * This slot marks the current selected payee as modified (dirty). */ void slotPayeeDataChanged(); void slotKeyListChanged(); /** * This slot is called when the name of a payee is changed inside * the payee list view and only a single payee is selected. */ void slotRenamePayee(QListWidgetItem *p); /** * Updates the payee data in m_payee from the information in the * payee information widget. */ void slotUpdatePayee(); void slotSelectTransaction(); void slotPayeeNew(); void slotRenameButtonCliked(); void slotChangeFilter(int index); private slots: /** * This slot receives the signal from the listview control that an item was right-clicked, * If @p points to a real payee item, emits openContextMenu(). * * @param p position of the pointer device */ void slotOpenContextMenu(const QPoint& p); void slotChooseDefaultAccount(); /** * Fetches the payee data from addressbook. */ void slotSyncAddressBook(); void slotContactFetched(const ContactData &identity); /** * Creates mail to payee. */ void slotSendMail(); signals: void transactionSelected(const QString& accountId, const QString& transactionId); void openContextMenu(const MyMoneyObject& obj); void selectObjects(const QList& payees); /** * This signal is emitted whenever the view is about to be shown. */ void aboutToShow(); private: MyMoneyPayee m_payee; QString m_newName; MyMoneyContact *m_contact; int m_payeeRow; QList m_payeeRows; /** * List of selected payees */ QList m_selectedPayeesList; /** * This member holds a list of all transactions */ QList > m_transactionList; /** * This member holds the state of the toggle switch used * to suppress updates due to MyMoney engine data changes */ bool m_needReload; /** * This member holds the load state of page */ bool m_needLoad; /** * Search widget for the list */ KListWidgetSearchLine* m_searchWidget; /** * Semaphore to suppress loading during selection */ bool m_inSelection; /** * This signals whether a payee can be edited **/ bool m_allowEditing; /** * This holds the filter type */ int m_payeeFilterType; AccountNamesFilterProxyModel *m_filterProxyModel; /** Checks whether the currently selected payee is "dirty" * @return true, if payee is modified (is "dirty"); false otherwise */ bool isDirty() const; /** Sets the payee's "dirty" (modified) status * @param dirty if true (default), payee will be set to dirty */ void setDirty(bool dirty = true); /** Initializes page and sets its load status to initialized */ void init(); }; #endif diff --git a/kmymoney/views/kscheduledview.h b/kmymoney/views/kscheduledview.h index 5c7892d49..a3fcb1508 100644 --- a/kmymoney/views/kscheduledview.h +++ b/kmymoney/views/kscheduledview.h @@ -1,161 +1,161 @@ /*************************************************************************** kscheduledview.h - description ------------------- begin : Sun Jan 27 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KSCHEDULEDVIEW_H #define KSCHEDULEDVIEW_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_kscheduledviewdecl.h" /** * Contains all the scheduled transactions be they bills, deposits or transfers. * Encapsulates all the operations including adding, editing and deleting. * Used by the KMyMoneyView class to show the view. * * @author Michael Edwardes 2000-2002 * $Id: kscheduledview.h,v 1.33 2009/03/01 19:13:08 ipwizard Exp $ * * @short A class to encapsulate recurring transaction operations. */ class KTreeWidgetSearchLineWidget; class MyMoneySchedule; class KScheduledView : public QWidget, private Ui::KScheduledViewDecl { Q_OBJECT public: /** * Standard constructor for QWidgets. */ - KScheduledView(QWidget *parent = 0); + explicit KScheduledView(QWidget *parent = 0); /** * Standard destructor. */ ~KScheduledView(); void setDefaultFocus(); /** * Called by KMyMoneyView. */ void showEvent(QShowEvent* event); public slots: void slotSelectSchedule(const QString& schedule); void slotReloadView(); signals: void scheduleSelected(const MyMoneySchedule& schedule); void openContextMenu(); void skipSchedule(); void enterSchedule(); void editSchedule(); /** * This signal is emitted whenever the view is about to be shown. */ void aboutToShow(); protected slots: /** * Shows the context menu when the user right clicks or presses * a 'windows' key when an item is selected. * * @param pos The position where to popup * @return none **/ void slotListViewContextMenu(const QPoint& pos); void slotListItemExecuted(QTreeWidgetItem*, int); void slotAccountActivated(); void slotListViewCollapsed(QTreeWidgetItem* item); void slotListViewExpanded(QTreeWidgetItem* item); void slotBriefSkipClicked(const MyMoneySchedule& schedule, const QDate&); void slotBriefEnterClicked(const MyMoneySchedule& schedule, const QDate&); void slotTimerDone(); void slotSetSelectedItem(); void slotRearrange(); protected: QTreeWidgetItem* addScheduleItem(QTreeWidgetItem* parent, MyMoneySchedule& schedule); private: /// The selected schedule id in the list view. QString m_selectedSchedule; /// Read config file void readConfig(); /// Write config file void writeConfig(); /** * Refresh the view. */ void refresh(bool full = true, const QString& schedId = QString()); /** * Loads the accounts into the combo box. */ // void loadAccounts(); QMenu *m_kaccPopup; QStringList m_filterAccounts; bool m_openBills; bool m_openDeposits; bool m_openTransfers; bool m_openLoans; bool m_needReload; /** * This member holds the load state of page */ bool m_needLoad; /** * Search widget for the list */ KTreeWidgetSearchLineWidget* m_searchWidget; /** Initializes page and sets its load status to initialized */ void init(); }; #endif diff --git a/kmymoney/views/kscheduletreeitem.h b/kmymoney/views/kscheduletreeitem.h index 6ea460d72..712188d03 100644 --- a/kmymoney/views/kscheduletreeitem.h +++ b/kmymoney/views/kscheduletreeitem.h @@ -1,53 +1,53 @@ /*************************************************************************** kscheduletreeitem.h - description ------------------- begin : Fri Jul 16 2010 copyright : (C) 2010 by Alvaro Soliverez email : asoliverez@kde.org ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef KSCHEDULETREEITEM_H #define KSCHEDULETREEITEM_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class KScheduleTreeItem : public QTreeWidgetItem { public: - KScheduleTreeItem(QTreeWidget* parent); + explicit KScheduleTreeItem(QTreeWidget* parent); - KScheduleTreeItem(QTreeWidgetItem* &parent); + explicit KScheduleTreeItem(QTreeWidgetItem* &parent); bool operator<(const QTreeWidgetItem &otherItem) const; enum ScheduleItemDataRole { ScheduleIdRole = Qt::UserRole, OrderRole = Qt::UserRole + 1 }; }; #endif // KSCHEDULETREEITEM_H diff --git a/kmymoney/views/ktagsview_p.h b/kmymoney/views/ktagsview_p.h index e86335a73..ca36c8d82 100644 --- a/kmymoney/views/ktagsview_p.h +++ b/kmymoney/views/ktagsview_p.h @@ -1,183 +1,182 @@ /*************************************************************************** ktagsview_p.h ------------- begin : Sat Oct 13 2012 copyright : (C) 2012 by Alessandro Russo (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KTAGSVIEW_P_H #define KTAGSVIEW_P_H #include "ktagsview.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_ktagsview.h" #include "kmymoneyviewbase_p.h" #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "mymoneytag.h" #include "mymoneytransactionfilter.h" #include "icons.h" #include "viewenums.h" #include "widgetenums.h" using namespace Icons; namespace Ui { class KTagsView; } class KTagsViewPrivate : public KMyMoneyViewBasePrivate { - Q_DECLARE_PUBLIC(KTagsView) + Q_DECLARE_PUBLIC(KTagsView) public: - - KTagsViewPrivate(KTagsView *qq) : - q_ptr(qq), - ui(new Ui::KTagsView) - { - } - - ~KTagsViewPrivate() - { - delete ui; - } - - void init() - { - Q_Q(KTagsView); - m_needLoad = false; - ui->setupUi(q); - - // create the searchline widget - // and insert it into the existing layout - m_searchWidget = new KListWidgetSearchLine(q, ui->m_tagsList); - m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); - ui->m_tagsList->setContextMenuPolicy(Qt::CustomContextMenu); - ui->m_listTopHLayout->insertWidget(0, m_searchWidget); - - //load the filter type - ui->m_filterBox->addItem(i18nc("@item Show all tags", "All")); - ui->m_filterBox->addItem(i18nc("@item Show only used tags", "Used")); - ui->m_filterBox->addItem(i18nc("@item Show only unused tags", "Unused")); - ui->m_filterBox->addItem(i18nc("@item Show only opened tags", "Opened")); - ui->m_filterBox->addItem(i18nc("@item Show only closed tags", "Closed")); - ui->m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); - - ui->m_newButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListAddTag])); - ui->m_renameButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditRename])); - ui->m_deleteButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListRemoveTag])); - ui->m_updateButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogOK])); - ui->m_updateButton->setEnabled(false); - - ui->m_register->setupRegister(MyMoneyAccount(), - QList { eWidgets::eTransaction::Column::Date, - eWidgets::eTransaction::Column::Account, - eWidgets::eTransaction::Column::Detail, - eWidgets::eTransaction::Column::ReconcileFlag, - eWidgets::eTransaction::Column::Payment, - eWidgets::eTransaction::Column::Deposit - }); - ui->m_register->setSelectionMode(QTableWidget::SingleSelection); - ui->m_register->setDetailsColumnType(eWidgets::eRegister::DetailColumn::AccountFirst); - ui->m_balanceLabel->hide(); - - q->connect(ui->m_tagsList, &QListWidget::currentItemChanged, q, static_cast(&KTagsView::slotSelectTag)); - q->connect(ui->m_tagsList, &QListWidget::itemSelectionChanged, q, static_cast(&KTagsView::slotSelectTag)); - q->connect(ui->m_tagsList, &QListWidget::itemDoubleClicked, q, &KTagsView::slotStartRename); - q->connect(ui->m_tagsList, &QListWidget::itemChanged, q, &KTagsView::slotRenameTag); - q->connect(ui->m_tagsList, &QWidget::customContextMenuRequested, q, &KTagsView::slotOpenContextMenu); - - q->connect(ui->m_newButton, &QAbstractButton::clicked, q, &KTagsView::tagNewClicked); - q->connect(ui->m_renameButton, &QAbstractButton::clicked, q, &KTagsView::slotRenameButtonCliked); - q->connect(ui->m_deleteButton, &QAbstractButton::clicked, q, &KTagsView::tagDeleteClicked); - - q->connect(ui->m_colorbutton, &KColorButton::changed, q, &KTagsView::slotTagDataChanged); - q->connect(ui->m_closed, &QCheckBox::stateChanged, q, &KTagsView::slotTagDataChanged); - q->connect(ui->m_notes, &QTextEdit::textChanged, q, &KTagsView::slotTagDataChanged); - - q->connect(ui->m_updateButton, &QAbstractButton::clicked, q, &KTagsView::slotUpdateTag); - q->connect(ui->m_helpButton, &QAbstractButton::clicked, q, &KTagsView::slotHelp); - - q->connect(ui->m_register, &KMyMoneyRegister::Register::editTransaction, q, &KTagsView::slotSelectTransaction); - - q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KTagsView::refresh); - - q->connect(ui->m_filterBox, static_cast(&QComboBox::currentIndexChanged), q, &KTagsView::slotChangeFilter); - - // use the size settings of the last run (if any) - auto grp = KSharedConfig::openConfig()->group("Last Use Settings"); - ui->m_splitter->restoreState(grp.readEntry("KTagsViewSplitterSize", QByteArray())); - ui->m_splitter->setChildrenCollapsible(false); - - // At start we haven't any tag selected - ui->m_tabWidget->setEnabled(false); // disable tab widget - ui->m_deleteButton->setEnabled(false); // disable delete and rename button - ui->m_renameButton->setEnabled(false); - m_tag = MyMoneyTag(); // make sure we don't access an undefined tag - q->clearItemData(); - } - - KTagsView *q_ptr; - Ui::KTagsView *ui; - - MyMoneyTag m_tag; - QString m_newName; - - /** + explicit KTagsViewPrivate(KTagsView *qq) : + q_ptr(qq), + ui(new Ui::KTagsView) + { + } + + ~KTagsViewPrivate() + { + delete ui; + } + + void init() + { + Q_Q(KTagsView); + m_needLoad = false; + ui->setupUi(q); + + // create the searchline widget + // and insert it into the existing layout + m_searchWidget = new KListWidgetSearchLine(q, ui->m_tagsList); + m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + ui->m_tagsList->setContextMenuPolicy(Qt::CustomContextMenu); + ui->m_listTopHLayout->insertWidget(0, m_searchWidget); + + //load the filter type + ui->m_filterBox->addItem(i18nc("@item Show all tags", "All")); + ui->m_filterBox->addItem(i18nc("@item Show only used tags", "Used")); + ui->m_filterBox->addItem(i18nc("@item Show only unused tags", "Unused")); + ui->m_filterBox->addItem(i18nc("@item Show only opened tags", "Opened")); + ui->m_filterBox->addItem(i18nc("@item Show only closed tags", "Closed")); + ui->m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + + ui->m_newButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListAddTag])); + ui->m_renameButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditRename])); + ui->m_deleteButton->setIcon(QIcon::fromTheme(g_Icons[Icon::ListRemoveTag])); + ui->m_updateButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogOK])); + ui->m_updateButton->setEnabled(false); + + ui->m_register->setupRegister(MyMoneyAccount(), + QList { eWidgets::eTransaction::Column::Date, + eWidgets::eTransaction::Column::Account, + eWidgets::eTransaction::Column::Detail, + eWidgets::eTransaction::Column::ReconcileFlag, + eWidgets::eTransaction::Column::Payment, + eWidgets::eTransaction::Column::Deposit + }); + ui->m_register->setSelectionMode(QTableWidget::SingleSelection); + ui->m_register->setDetailsColumnType(eWidgets::eRegister::DetailColumn::AccountFirst); + ui->m_balanceLabel->hide(); + + q->connect(ui->m_tagsList, &QListWidget::currentItemChanged, q, static_cast(&KTagsView::slotSelectTag)); + q->connect(ui->m_tagsList, &QListWidget::itemSelectionChanged, q, static_cast(&KTagsView::slotSelectTag)); + q->connect(ui->m_tagsList, &QListWidget::itemDoubleClicked, q, &KTagsView::slotStartRename); + q->connect(ui->m_tagsList, &QListWidget::itemChanged, q, &KTagsView::slotRenameTag); + q->connect(ui->m_tagsList, &QWidget::customContextMenuRequested, q, &KTagsView::slotOpenContextMenu); + + q->connect(ui->m_newButton, &QAbstractButton::clicked, q, &KTagsView::tagNewClicked); + q->connect(ui->m_renameButton, &QAbstractButton::clicked, q, &KTagsView::slotRenameButtonCliked); + q->connect(ui->m_deleteButton, &QAbstractButton::clicked, q, &KTagsView::tagDeleteClicked); + + q->connect(ui->m_colorbutton, &KColorButton::changed, q, &KTagsView::slotTagDataChanged); + q->connect(ui->m_closed, &QCheckBox::stateChanged, q, &KTagsView::slotTagDataChanged); + q->connect(ui->m_notes, &QTextEdit::textChanged, q, &KTagsView::slotTagDataChanged); + + q->connect(ui->m_updateButton, &QAbstractButton::clicked, q, &KTagsView::slotUpdateTag); + q->connect(ui->m_helpButton, &QAbstractButton::clicked, q, &KTagsView::slotHelp); + + q->connect(ui->m_register, &KMyMoneyRegister::Register::editTransaction, q, &KTagsView::slotSelectTransaction); + + q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KTagsView::refresh); + + q->connect(ui->m_filterBox, static_cast(&QComboBox::currentIndexChanged), q, &KTagsView::slotChangeFilter); + + // use the size settings of the last run (if any) + auto grp = KSharedConfig::openConfig()->group("Last Use Settings"); + ui->m_splitter->restoreState(grp.readEntry("KTagsViewSplitterSize", QByteArray())); + ui->m_splitter->setChildrenCollapsible(false); + + // At start we haven't any tag selected + ui->m_tabWidget->setEnabled(false); // disable tab widget + ui->m_deleteButton->setEnabled(false); // disable delete and rename button + ui->m_renameButton->setEnabled(false); + m_tag = MyMoneyTag(); // make sure we don't access an undefined tag + q->clearItemData(); + } + + KTagsView *q_ptr; + Ui::KTagsView *ui; + + MyMoneyTag m_tag; + QString m_newName; + + /** * This member holds a list of all transactions */ - QList > m_transactionList; + QList > m_transactionList; - /** + /** * This member holds the load state of page */ - bool m_needLoad; + bool m_needLoad; - /** + /** * Search widget for the list */ - KListWidgetSearchLine* m_searchWidget; + KListWidgetSearchLine* m_searchWidget; - /** + /** * Semaphore to suppress loading during selection */ - bool m_inSelection; + bool m_inSelection; - /** + /** * This signals whether a tag can be edited **/ - bool m_allowEditing; + bool m_allowEditing; - /** + /** * This holds the filter type */ - int m_tagFilterType; + int m_tagFilterType; }; #endif diff --git a/kmymoney/views/ledgerdelegate.h b/kmymoney/views/ledgerdelegate.h index 093635e51..e36bc70f4 100644 --- a/kmymoney/views/ledgerdelegate.h +++ b/kmymoney/views/ledgerdelegate.h @@ -1,109 +1,109 @@ /*************************************************************************** ledgerdelegate.h ------------------- begin : Sat Aug 8 2015 copyright : (C) 2015 by Thomas Baumgart email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef LEDGERDELEGATE_H #define LEDGERDELEGATE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ledgermodel.h" #include "modelenums.h" class LedgerView; class LedgerSeperator { public: - LedgerSeperator(eLedgerModel::Role role) : m_role(role) {} + explicit LedgerSeperator(eLedgerModel::Role role) : m_role(role) {} virtual ~LedgerSeperator() {} virtual bool rowHasSeperator(const QModelIndex& index) const = 0; virtual QString seperatorText(const QModelIndex& index) const = 0; static void setFirstFiscalDate(int firstMonth, int firstDay); static void setShowFiscalDate(bool show) { showFiscalDate = show; } static void setShowFancyDate(bool show) { showFancyDate = show; } protected: inline QModelIndex nextIndex(const QModelIndex& index) const; eLedgerModel::Role m_role; static QDate firstFiscalDate; static bool showFiscalDate; static bool showFancyDate; }; class LedgerDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit LedgerDelegate(LedgerView* parent = 0); virtual ~LedgerDelegate(); virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; virtual void setEditorData(QWidget* editWidget, const QModelIndex& index) const; virtual void setSortRole(eLedgerModel::Role role); virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; /** * This method returns the row that currently has an editor * or -1 if no editor is open */ virtual int editorRow() const; static void setErroneousColor(const QColor& color); static void setImportedColor(const QColor& color); static QColor erroneousColor(); protected: bool eventFilter(QObject* o, QEvent* event); protected Q_SLOTS: void endEdit(); Q_SIGNALS: void sizeHintChanged(const QModelIndex&) const; private: class Private; Private * const d; static QColor m_erroneousColor; static QColor m_importedColor; static QColor m_seperatorColor; }; #endif // LEDGERDELEGATE_H diff --git a/kmymoney/views/payeeidentifierselectiondelegate.h b/kmymoney/views/payeeidentifierselectiondelegate.h index 78bd8bb6f..e715602a4 100644 --- a/kmymoney/views/payeeidentifierselectiondelegate.h +++ b/kmymoney/views/payeeidentifierselectiondelegate.h @@ -1,50 +1,50 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 PAYEEIDENTIFIERSELECTIONDELEGATE_H #define PAYEEIDENTIFIERSELECTIONDELEGATE_H #include #include class payeeIdentifierSelectionDelegate : public QStyledItemDelegate { Q_OBJECT public: - payeeIdentifierSelectionDelegate(QObject* parent = 0); + explicit payeeIdentifierSelectionDelegate(QObject* parent = 0); virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; }; class payeeIdentifierTypeSelectionWidget : public QComboBox { Q_OBJECT public: explicit payeeIdentifierTypeSelectionWidget(QWidget* parent = 0); signals: void commitData(QWidget* editor); private slots: void itemSelected(int index); }; #endif // PAYEEIDENTIFIERSELECTIONDELEGATE_H diff --git a/kmymoney/widgets/accountsviewproxymodel.h b/kmymoney/widgets/accountsviewproxymodel.h index f163e3146..6fa5a0c04 100644 --- a/kmymoney/widgets/accountsviewproxymodel.h +++ b/kmymoney/widgets/accountsviewproxymodel.h @@ -1,71 +1,71 @@ /*************************************************************************** * Copyright 2010 Cristian Onet onet.cristian@gmail.com * * Copyright 2017 Łukasz Wojniłowicz lukasz.wojnilowicz@gmail.com * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 ACCOUNTSVIEWPROXYMODEL_H #define ACCOUNTSVIEWPROXYMODEL_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "accountsproxymodel.h" #include "modelenums.h" class QPoint; /** * This model is specialized to organize the data for the accounts tree view * based on the data of the @ref AccountsModel. */ class AccountsViewProxyModel : public AccountsProxyModel { Q_OBJECT public: - AccountsViewProxyModel(QObject *parent = nullptr); + explicit AccountsViewProxyModel(QObject *parent = nullptr); ~AccountsViewProxyModel(); void setColumnVisibility(eAccountsModel::Column column, bool visible); QSet getVisibleColumns(); public slots: void slotColumnsMenu(const QPoint); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; private: QSet m_visColumns; signals: void columnToggled(const eAccountsModel::Column column, const bool show); }; #endif diff --git a/kmymoney/widgets/amountedit.cpp b/kmymoney/widgets/amountedit.cpp index 14a1dfb1a..0fd4985bf 100644 --- a/kmymoney/widgets/amountedit.cpp +++ b/kmymoney/widgets/amountedit.cpp @@ -1,433 +1,433 @@ /*************************************************************************** amountedit.cpp ------------------- copyright : (C) 2016 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #include "amountedit.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "amountvalidator.h" #include "kmymoneycalculator.h" #include "mymoneymoney.h" #include "mymoneysecurity.h" #include "icons/icons.h" using namespace Icons; class AmountEditPrivate { Q_DISABLE_COPY(AmountEditPrivate) Q_DECLARE_PUBLIC(AmountEdit) public: - AmountEditPrivate(AmountEdit* qq) : + explicit AmountEditPrivate(AmountEdit* qq) : q_ptr(qq), m_allowEmpty(false) { Q_Q(AmountEdit); m_calculatorFrame = new QFrame(q); m_calculatorFrame->setWindowFlags(Qt::Popup); m_calculatorFrame->setFrameStyle(QFrame::Panel | QFrame::Raised); m_calculatorFrame->setLineWidth(3); m_calculator = new KMyMoneyCalculator(m_calculatorFrame); m_calculatorFrame->hide(); } void init() { Q_Q(AmountEdit); // Yes, just a simple double validator ! auto validator = new AmountValidator(q); q->setValidator(validator); q->setAlignment(Qt::AlignRight | Qt::AlignVCenter); int height = q->sizeHint().height(); int btnSize = q->sizeHint().height() - 5; m_calculatorButton = new QToolButton(q); m_calculatorButton->setIcon(QIcon::fromTheme(g_Icons[Icon::AccessoriesCalculator])); m_calculatorButton->setCursor(Qt::ArrowCursor); m_calculatorButton->setStyleSheet("QToolButton { border: none; padding: 2px}"); m_calculatorButton->setFixedSize(btnSize, btnSize); m_calculatorButton->show(); int frameWidth = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); q->setStyleSheet(QString("QLineEdit { padding-right: %1px }") .arg(btnSize - frameWidth)); q->setMinimumHeight(height); q->connect(m_calculatorButton, &QAbstractButton::clicked, q, &AmountEdit::slotCalculatorOpen); KSharedConfig::Ptr kconfig = KSharedConfig::openConfig(); KConfigGroup grp = kconfig->group("General Options"); if (grp.readEntry("DontShowCalculatorButton", false) == true) q->setCalculatorButtonVisible(false); q->connect(q, &QLineEdit::textChanged, q, &AmountEdit::theTextChanged); q->connect(m_calculator, &KMyMoneyCalculator::signalResultAvailable, q, &AmountEdit::slotCalculatorResult); q->connect(m_calculator, &KMyMoneyCalculator::signalQuit, q, &AmountEdit::slotCalculatorClose); } /** * Internal helper function for value() and ensureFractionalPart(). */ void ensureFractionalPart(QString& s) const { s = MyMoneyMoney(s).formatMoney(QString(), m_prec, false); } /** * This method opens the calculator and replays the key * event pointed to by @p ev. If @p ev is 0, then no key * event is replayed. * * @param ev pointer to QKeyEvent that started the calculator. */ void calculatorOpen(QKeyEvent* k) { Q_Q(AmountEdit); m_calculator->setInitialValues(q->text(), k); auto h = m_calculatorFrame->height(); auto w = m_calculatorFrame->width(); // usually, the calculator widget is shown underneath the MoneyEdit widget // if it does not fit on the screen, we show it above this widget auto p = q->mapToGlobal(QPoint(0, 0)); if (p.y() + q->height() + h > QApplication::desktop()->height()) p.setY(p.y() - h); else p.setY(p.y() + q->height()); // usually, it is shown left aligned. If it does not fit, we align it // to the right edge of the widget if (p.x() + w > QApplication::desktop()->width()) p.setX(p.x() + q->width() - w); QRect r = m_calculator->geometry(); r.moveTopLeft(p); m_calculatorFrame->setGeometry(r); m_calculatorFrame->show(); m_calculator->setFocus(); } AmountEdit* q_ptr; QFrame* m_calculatorFrame; KMyMoneyCalculator* m_calculator; QToolButton* m_calculatorButton; int m_prec; bool m_allowEmpty; QString m_previousText; // keep track of what has been typed QString m_text; // keep track of what was the original value /** * This holds the number of precision to be used * when no other information (e.g. from account) * is available. * * @sa setStandardPrecision() */ static int standardPrecision; }; int AmountEditPrivate::standardPrecision = 2; AmountEdit::AmountEdit(QWidget *parent, const int prec) : QLineEdit(parent), d_ptr(new AmountEditPrivate(this)) { Q_D(AmountEdit); d->m_prec = prec; if (prec < -1 || prec > 20) { d->m_prec = AmountEditPrivate::standardPrecision; } d->init(); } AmountEdit::AmountEdit(const MyMoneySecurity& sec, QWidget *parent) : QLineEdit(parent), d_ptr(new AmountEditPrivate(this)) { Q_D(AmountEdit); d->m_prec = MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()); d->init(); } AmountEdit::~AmountEdit() { Q_D(AmountEdit); delete d; } void AmountEdit::setStandardPrecision(int prec) { if (prec >= 0 && prec < 20) { AmountEditPrivate::standardPrecision = prec; } } void AmountEdit::resizeEvent(QResizeEvent* event) { Q_D(AmountEdit); Q_UNUSED(event); const int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); d->m_calculatorButton->move(width() - d->m_calculatorButton->width() - frameWidth - 2, 2); } void AmountEdit::focusOutEvent(QFocusEvent* event) { Q_D(AmountEdit); QLineEdit::focusOutEvent(event); // make sure we have a zero value in case the current text // is empty but this is not allowed if (text().isEmpty() && !d->m_allowEmpty) { QLineEdit::setText(QLatin1String("0")); } // make sure we have a fractional part if (!text().isEmpty()) ensureFractionalPart(); // in case the widget contains a different value we emit // the valueChanged signal if (MyMoneyMoney(text()) != MyMoneyMoney(d->m_text)) { emit valueChanged(text()); } } void AmountEdit::keyPressEvent(QKeyEvent* event) { Q_D(AmountEdit); switch(event->key()) { case Qt::Key_Plus: case Qt::Key_Minus: if (hasSelectedText()) { cut(); } if (text().length() == 0) { QLineEdit::keyPressEvent(event); break; } // in case of '-' we do not enter the calculator when // the current position is the beginning and there is // no '-' sign at the first position. if (event->key() == Qt::Key_Minus) { if (cursorPosition() == 0 && text()[0] != '-') { QLineEdit::keyPressEvent(event); break; } } // intentional fall through case Qt::Key_Slash: case Qt::Key_Asterisk: case Qt::Key_Percent: if (hasSelectedText()) { // remove the selected text cut(); } d->calculatorOpen(event); break; default: QLineEdit::keyPressEvent(event); break; } } void AmountEdit::setPrecision(const int prec) { Q_D(AmountEdit); if (prec >= -1 && prec <= 20) { if (prec != d->m_prec) { d->m_prec = prec; // update current display setValue(value()); } } } int AmountEdit::precision() const { Q_D(const AmountEdit); return d->m_prec; } bool AmountEdit::isValid() const { return !(text().isEmpty()); } QString AmountEdit::numericalText() const { return value().toString(); } MyMoneyMoney AmountEdit::value() const { Q_D(const AmountEdit); MyMoneyMoney money(text()); if (d->m_prec != -1) money = money.convert(MyMoneyMoney::precToDenom(d->m_prec)); return money; } void AmountEdit::setValue(const MyMoneyMoney& value) { Q_D(AmountEdit); // load the value into the widget but don't use thousandsSeparators setText(value.formatMoney(QString(), d->m_prec, false)); } void AmountEdit::setText(const QString& txt) { Q_D(AmountEdit); d->m_text = txt; if (isEnabled() && !txt.isEmpty()) d->ensureFractionalPart(d->m_text); QLineEdit::setText(d->m_text); #if 0 m_resetButton->setEnabled(false); #endif } void AmountEdit::resetText() { #if 0 Q_D(AmountEdit); setText(d->m_text); m_resetButton->setEnabled(false); #endif } void AmountEdit::theTextChanged(const QString & theText) { Q_D(AmountEdit); QLocale locale; QString dec = locale.groupSeparator(); QString l_text = theText; QString nsign, psign; nsign = locale.negativeSign(); psign = locale.positiveSign(); auto i = 0; if (isEnabled()) { QValidator::State state = validator()->validate(l_text, i); if (state == QValidator::Intermediate) { if (l_text.length() == 1) { if (l_text != dec && l_text != nsign && l_text != psign) state = QValidator::Invalid; } } if (state == QValidator::Invalid) QLineEdit::setText(d->m_previousText); else { d->m_previousText = l_text; emit validatedTextChanged(text()); } } } void AmountEdit::slotCalculatorOpen() { Q_D(AmountEdit); d->calculatorOpen(0); } void AmountEdit::slotCalculatorClose() { Q_D(AmountEdit); if (d->m_calculator != 0) { d->m_calculatorFrame->hide(); } } void AmountEdit::slotCalculatorResult() { Q_D(AmountEdit); slotCalculatorClose(); if (d->m_calculator != 0) { setText(d->m_calculator->result()); ensureFractionalPart(); #if 0 // I am not sure if getting a result from the calculator // is a good event to emit a value changed signal. We // should do this only on focusOutEvent() emit valueChanged(text()); d->m_text = text(); #endif } } void AmountEdit::setCalculatorButtonVisible(const bool show) { Q_D(AmountEdit); d->m_calculatorButton->setVisible(show); } void AmountEdit::setAllowEmpty(bool allowed) { Q_D(AmountEdit); d->m_allowEmpty = allowed; } bool AmountEdit::isEmptyAllowed() const { Q_D(const AmountEdit); return d->m_allowEmpty; } bool AmountEdit::isCalculatorButtonVisible() const { Q_D(const AmountEdit); return d->m_calculatorButton->isVisible(); } void AmountEdit::ensureFractionalPart() { Q_D(AmountEdit); QString s(text()); d->ensureFractionalPart(s); // by setting the text only when it's different then the one that it is already there // we preserve the edit widget's state (like the selection for example) during a // call to ensureFractionalPart() that does not change anything if (s != text()) QLineEdit::setText(s); } diff --git a/kmymoney/widgets/budgetviewproxymodel.h b/kmymoney/widgets/budgetviewproxymodel.h index c0d37b837..56d1c2e3d 100644 --- a/kmymoney/widgets/budgetviewproxymodel.h +++ b/kmymoney/widgets/budgetviewproxymodel.h @@ -1,77 +1,77 @@ /*************************************************************************** budgetviewproxymodel.h ------------------- Copyright (C) 2006 by Darren Gould Copyright (C) 2006 by Alvaro Soliverez Copyright (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef BUDGETVIEWPROXYMODEL_H #define BUDGETVIEWPROXYMODEL_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "accountsviewproxymodel.h" #include "mymoneymoney.h" #include "mymoneybudget.h" class MyMoneyAccount; /** * This proxy model implements all the functionality needed by the budgets * account tree based on the @ref AccountsModel. One such functionality is * obtaining the account balance and value base on the budget. * * @author Cristin Oneț */ class BudgetViewProxyModel : public AccountsViewProxyModel { Q_OBJECT public: - BudgetViewProxyModel(QObject *parent = 0); + explicit BudgetViewProxyModel(QObject *parent = 0); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; void setBudget(const MyMoneyBudget& budget); signals: /** * Emit this signal when the balance of the budget is changed. */ void balanceChanged(const MyMoneyMoney &); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; MyMoneyMoney accountBalance(const QString &accountId) const; MyMoneyMoney accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance) const; MyMoneyMoney computeTotalValue(const QModelIndex &source_index) const; private: void checkBalance(); private: MyMoneyBudget m_budget; MyMoneyMoney m_lastBalance; }; #endif diff --git a/kmymoney/widgets/creditdebithelper.cpp b/kmymoney/widgets/creditdebithelper.cpp index 5a87ac09d..9f39ef357 100644 --- a/kmymoney/widgets/creditdebithelper.cpp +++ b/kmymoney/widgets/creditdebithelper.cpp @@ -1,137 +1,137 @@ /*************************************************************************** creditdebithelper.cpp ------------------- copyright : (C) 2016 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #include "creditdebithelper.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "amountedit.h" class CreditDebitHelperPrivate { Q_DISABLE_COPY(CreditDebitHelperPrivate) Q_DECLARE_PUBLIC(CreditDebitHelper) public: - CreditDebitHelperPrivate(CreditDebitHelper *qq) : + explicit CreditDebitHelperPrivate(CreditDebitHelper *qq) : q_ptr(qq) { } void widgetChanged(AmountEdit* src, AmountEdit* dst) { // make sure the objects exist if(!src || !dst) { return; } // in case both are filled with text, the src wins if(!src->text().isEmpty() && !dst->text().isEmpty()) { dst->clear(); } // in case the source is negative, we negate the value // and load it into destination. if(src->value().isNegative()) { dst->setValue(-(src->value())); src->clear(); } Q_Q(CreditDebitHelper); emit q->valueChanged(); } CreditDebitHelper *q_ptr; QPointer m_credit; QPointer m_debit; }; CreditDebitHelper::CreditDebitHelper(QObject* parent, AmountEdit* credit, AmountEdit* debit) : QObject(parent), d_ptr(new CreditDebitHelperPrivate(this)) { Q_D(CreditDebitHelper); d->m_credit = credit; d->m_debit = debit; connect(d->m_credit.data(), &AmountEdit::valueChanged, this, &CreditDebitHelper::creditChanged); connect(d->m_debit.data(), &AmountEdit::valueChanged, this, &CreditDebitHelper::debitChanged); } CreditDebitHelper::~CreditDebitHelper() { Q_D(CreditDebitHelper); delete d; } void CreditDebitHelper::creditChanged() { Q_D(CreditDebitHelper); d->widgetChanged(d->m_credit, d->m_debit); } void CreditDebitHelper::debitChanged() { Q_D(CreditDebitHelper); d->widgetChanged(d->m_debit, d->m_credit); } bool CreditDebitHelper::haveValue() const { Q_D(const CreditDebitHelper); return (!d->m_credit->text().isEmpty()) || (!d->m_debit->text().isEmpty()); } MyMoneyMoney CreditDebitHelper::value() const { Q_D(const CreditDebitHelper); MyMoneyMoney value; if(d->m_credit && d->m_debit) { if(!d->m_credit->text().isEmpty()) { value = -d->m_credit->value(); } else { value = d->m_debit->value(); } } else { qWarning() << "CreditDebitHelper::value() called with no objects attached. Zero returned."; } return value; } void CreditDebitHelper::setValue(const MyMoneyMoney& value) { Q_D(CreditDebitHelper); if(d->m_credit && d->m_debit) { if(value.isNegative()) { d->m_credit->setValue(-value); d->m_debit->clear(); } else { d->m_debit->setValue(value); d->m_credit->clear(); } } else { qWarning() << "CreditDebitHelper::setValue() called with no objects attached. Skipped."; } } diff --git a/kmymoney/widgets/daterangedlg.cpp b/kmymoney/widgets/daterangedlg.cpp index e04f9d03e..460f45a56 100644 --- a/kmymoney/widgets/daterangedlg.cpp +++ b/kmymoney/widgets/daterangedlg.cpp @@ -1,158 +1,158 @@ /*************************************************************************** daterange.cpp ------------------- copyright : (C) 2003, 2007 by Thomas Baumgart email : ipwizard@users.sourceforge.net (C) 2017 by Ł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. * * * ***************************************************************************/ #include "daterangedlg.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneytransactionfilter.h" #include "mymoneyenums.h" #include "kmymoneydateinput.h" #include "ui_daterangedlg.h" using namespace eMyMoney; class DateRangeDlgPrivate { Q_DISABLE_COPY(DateRangeDlgPrivate) Q_DECLARE_PUBLIC(DateRangeDlg) public: - DateRangeDlgPrivate(DateRangeDlg *qq) : + explicit DateRangeDlgPrivate(DateRangeDlg *qq) : q_ptr(qq), ui(new Ui::DateRangeDlg) { } ~DateRangeDlgPrivate() { delete ui; } void setupDatePage() { Q_Q(DateRangeDlg); for (auto i = (int)TransactionFilter::Date::All; i < (int)TransactionFilter::Date::LastDateItem; ++i) { MyMoneyTransactionFilter::translateDateRange(static_cast(i), m_startDates[i], m_endDates[i]); } q->connect(ui->m_dateRange, static_cast(&KMyMoneyPeriodCombo::currentIndexChanged), q, &DateRangeDlg::slotDateRangeSelectedByUser); q->connect(ui->m_fromDate, &KMyMoneyDateInput::dateChanged, q, &DateRangeDlg::slotDateChanged); q->connect(ui->m_toDate, &KMyMoneyDateInput::dateChanged, q, &DateRangeDlg::slotDateChanged); q->setDateRange(TransactionFilter::Date::All); } DateRangeDlg *q_ptr; Ui::DateRangeDlg *ui; QDate m_startDates[(int)eMyMoney::TransactionFilter::Date::LastDateItem]; QDate m_endDates[(int)eMyMoney::TransactionFilter::Date::LastDateItem]; }; DateRangeDlg::DateRangeDlg(QWidget *parent) : QWidget(parent), d_ptr(new DateRangeDlgPrivate(this)) { Q_D(DateRangeDlg); d->ui->setupUi(this); d->setupDatePage(); } DateRangeDlg::~DateRangeDlg() { Q_D(DateRangeDlg); delete d; } void DateRangeDlg::slotReset() { Q_D(DateRangeDlg); d->ui->m_dateRange->setCurrentItem(TransactionFilter::Date::All); setDateRange(TransactionFilter::Date::All); } void DateRangeDlg::slotDateRangeSelectedByUser() { Q_D(DateRangeDlg); setDateRange(d->ui->m_dateRange->currentItem()); } void DateRangeDlg::setDateRange(const QDate& from, const QDate& to) { Q_D(DateRangeDlg); d->ui->m_fromDate->loadDate(from); d->ui->m_toDate->loadDate(to); d->ui->m_dateRange->setCurrentItem(TransactionFilter::Date::UserDefined); emit rangeChanged(); } void DateRangeDlg::setDateRange(TransactionFilter::Date idx) { Q_D(DateRangeDlg); d->ui->m_dateRange->setCurrentItem(idx); switch (idx) { case TransactionFilter::Date::All: d->ui->m_fromDate->loadDate(QDate()); d->ui->m_toDate->loadDate(QDate()); break; case TransactionFilter::Date::UserDefined: break; default: d->ui->m_fromDate->blockSignals(true); d->ui->m_toDate->blockSignals(true); d->ui->m_fromDate->loadDate(d->m_startDates[(int)idx]); d->ui->m_toDate->loadDate(d->m_endDates[(int)idx]); d->ui->m_fromDate->blockSignals(false); d->ui->m_toDate->blockSignals(false); break; } emit rangeChanged(); } TransactionFilter::Date DateRangeDlg::dateRange() const { Q_D(const DateRangeDlg); return d->ui->m_dateRange->currentItem(); } void DateRangeDlg::slotDateChanged() { Q_D(DateRangeDlg); d->ui->m_dateRange->blockSignals(true); d->ui->m_dateRange->setCurrentItem(TransactionFilter::Date::UserDefined); d->ui->m_dateRange->blockSignals(false); } QDate DateRangeDlg::fromDate() const { Q_D(const DateRangeDlg); return d->ui->m_fromDate->date(); } QDate DateRangeDlg::toDate() const { Q_D(const DateRangeDlg); return d->ui->m_toDate->date(); } diff --git a/kmymoney/widgets/kmymoneyaccountcombo.h b/kmymoney/widgets/kmymoneyaccountcombo.h index fa358a965..d361be47f 100644 --- a/kmymoney/widgets/kmymoneyaccountcombo.h +++ b/kmymoney/widgets/kmymoneyaccountcombo.h @@ -1,168 +1,168 @@ /*************************************************************************** kmymoneyaccountcombo - description ------------------- begin : Mon May 31 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KMYMONEYACCOUNTCOMBO_H #define KMYMONEYACCOUNTCOMBO_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "accountsproxymodel.h" #include "onlinebankingaccountsfilterproxymodel.h" /** * A proxy model used to filter all the data from the core accounts model leaving * only the name of the accounts so this model can be used in the account * completion combo. * * It shows only the first column (account name) and makes top level items non-selectable. * * @see AccountsModel * @see AccountsFilterProxyModel * * @author Cristian Onet 2010 * @author Christian David */ template class AccountNamesFilterProxyModelTpl : public baseProxyModel { public: - AccountNamesFilterProxyModelTpl(QObject *parent = 0); + explicit AccountNamesFilterProxyModelTpl(QObject *parent = 0); virtual Qt::ItemFlags flags(const QModelIndex &index) const; protected: bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const; }; /** * @brief "typedef" for AccountNamesFilterProxyModelTpl * * To create valid Qt moc data this class inherits the template and uses Q_OBJECT. * * @code typedef AccountNamesFilterProxyModelTpl AccountNamesFilterProxyModel; * should work as well */ class AccountNamesFilterProxyModel : public AccountNamesFilterProxyModelTpl { Q_OBJECT public: - AccountNamesFilterProxyModel(QObject* parent = 0) + explicit AccountNamesFilterProxyModel(QObject* parent = 0) : AccountNamesFilterProxyModelTpl< AccountsProxyModel >(parent) {} }; /** * @brief OnlineBankingAccountFilterProxyModel showing only the name column * * Is equivalent to AccountNamesFilterProxyModel using OnlineBankingAccountFilterProxyModel as base. */ typedef AccountNamesFilterProxyModelTpl OnlineBankingAccountNamesFilterProxyModel; /** * * * @author Cristian Onet */ class KMyMoneyAccountCombo : public KComboBox { Q_OBJECT Q_DISABLE_COPY(KMyMoneyAccountCombo) public: explicit KMyMoneyAccountCombo(QSortFilterProxyModel *model, QWidget* parent = nullptr); explicit KMyMoneyAccountCombo(QWidget* parent = nullptr); ~KMyMoneyAccountCombo(); void setSelected(const QString& id); const QString& getSelected() const; void setModel(QSortFilterProxyModel *model); /** * Overridden to get specific behavior */ void setEditable(bool isEditable); bool eventFilter(QObject* o, QEvent* e) override; public slots: void expandAll(); void collapseAll(); void showPopup() override; void hidePopup() override; protected: void wheelEvent(QWheelEvent *ev) override; protected slots: void activated(); void makeCompletion(const QString& txt) override; void selectItem(const QModelIndex& index); signals: void accountSelected(const QString&); private: class Private; QScopedPointer const d; }; template AccountNamesFilterProxyModelTpl::AccountNamesFilterProxyModelTpl(QObject *parent) : baseProxyModel(parent) { } /** * Top items are not selectable because they are not real accounts but are only used for grouping. */ template Qt::ItemFlags AccountNamesFilterProxyModelTpl::flags(const QModelIndex &index) const { if (!index.parent().isValid()) return baseProxyModel::flags(index) & ~Qt::ItemIsSelectable; return baseProxyModel::flags(index); } /** * Filter all but the first column. */ template bool AccountNamesFilterProxyModelTpl::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const { Q_UNUSED(source_parent) if (source_column == 0) return true; return false; } #endif diff --git a/kmymoney/widgets/kmymoneyaccounttreeview.cpp b/kmymoney/widgets/kmymoneyaccounttreeview.cpp index fef4b17d5..62017c680 100644 --- a/kmymoney/widgets/kmymoneyaccounttreeview.cpp +++ b/kmymoney/widgets/kmymoneyaccounttreeview.cpp @@ -1,257 +1,257 @@ /*************************************************************************** * Copyright 2010 Cristian Onet onet.cristian@gmail.com * * Copyright 2017 Łukasz Wojniłowicz lukasz.wojnilowicz@gmail.com * * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 "kmymoneyaccounttreeview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "models.h" #include "accountsmodel.h" #include "accountsviewproxymodel.h" #include "budgetviewproxymodel.h" #include "modelenums.h" #include "mymoneyenums.h" #include "viewenums.h" class KMyMoneyAccountTreeViewPrivate { Q_DISABLE_COPY(KMyMoneyAccountTreeViewPrivate) Q_DECLARE_PUBLIC(KMyMoneyAccountTreeView) public: - KMyMoneyAccountTreeViewPrivate(KMyMoneyAccountTreeView *qq) : + explicit KMyMoneyAccountTreeViewPrivate(KMyMoneyAccountTreeView *qq) : q_ptr(qq), m_view(View::None) { } ~KMyMoneyAccountTreeViewPrivate() { } QVector getVisibleGroups(const View view) { switch (view) { case View::Institutions: case View::Accounts: return QVector {eMyMoney::Account::Type::Asset, eMyMoney::Account::Type::Liability, eMyMoney::Account::Type::Equity}; case View::Categories: case View::Budget: return QVector {eMyMoney::Account::Type::Income, eMyMoney::Account::Type::Expense}; default: return QVector (); } } QSet readVisibleColumns(const View view) { QSet columns; const auto grp = KSharedConfig::openConfig()->group(getConfGrpName(view)); const auto cfgColumns = grp.readEntry("ColumnsSelection", QList()); columns.insert(eAccountsModel::Column::Account); foreach (const auto column, cfgColumns) columns.insert(static_cast(column)); return columns; } void openIndex(const QModelIndex &index) { Q_Q(KMyMoneyAccountTreeView); if (index.isValid()) { QVariant data = q->model()->data(index, (int)eAccountsModel::Role::Account); if (data.isValid()) { if (data.canConvert()) { emit q->openObject(data.value()); } if (data.canConvert()) { emit q->openObject(data.value()); } } } } static QString getConfGrpName(const View view) { switch (view) { case View::Institutions: return QStringLiteral("KInstitutionsView"); case View::Accounts: return QStringLiteral("KAccountsView"); case View::Categories: return QStringLiteral("KCategoriesView"); case View::Budget: return QStringLiteral("KBudgetsView"); default: return QString(); } } KMyMoneyAccountTreeView *q_ptr; AccountsViewProxyModel *m_model; View m_view; }; KMyMoneyAccountTreeView::KMyMoneyAccountTreeView(QWidget *parent) : QTreeView(parent), d_ptr(new KMyMoneyAccountTreeViewPrivate(this)) { setContextMenuPolicy(Qt::CustomContextMenu); // allow context menu to be opened on tree items header()->setContextMenuPolicy(Qt::CustomContextMenu); // allow context menu to be opened on tree header for columns selection connect(this, &QWidget::customContextMenuRequested, this, &KMyMoneyAccountTreeView::customContextMenuRequested); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setIconSize(QSize(22, 22)); setSortingEnabled(true); } KMyMoneyAccountTreeView::~KMyMoneyAccountTreeView() { Q_D(KMyMoneyAccountTreeView); if (d->m_view != View::None) { auto grp = KSharedConfig::openConfig()->group(d->getConfGrpName(d->m_view)); const auto columns = header()->saveState(); grp.writeEntry("HeaderState", columns); QList visColumns; foreach (const auto column, d->m_model->getVisibleColumns()) visColumns.append(static_cast(column)); grp.writeEntry("ColumnsSelection", visColumns); grp.sync(); } delete d; } AccountsViewProxyModel *KMyMoneyAccountTreeView::init(View view) { Q_D(KMyMoneyAccountTreeView); d->m_view = view; if (view != View::Budget) d->m_model = new AccountsViewProxyModel(this); else d->m_model = new BudgetViewProxyModel(this); d->m_model->addAccountGroup(d->getVisibleGroups(view)); const auto accountsModel = Models::instance()->accountsModel(); const auto institutionsModel = Models::instance()->institutionsModel(); AccountsModel *sourceModel; if (view != View::Institutions) sourceModel = accountsModel; else sourceModel = institutionsModel; foreach (const auto column, d->readVisibleColumns(view)) { d->m_model->setColumnVisibility(column, true); accountsModel->setColumnVisibility(column, true); institutionsModel->setColumnVisibility(column, true); } d->m_model->setSourceModel(sourceModel); d->m_model->setSourceColumns(sourceModel->getColumns()); setModel(d->m_model); connect(this->header(), &QWidget::customContextMenuRequested, d->m_model, &AccountsViewProxyModel::slotColumnsMenu); connect(d->m_model, &AccountsViewProxyModel::columnToggled, this, &KMyMoneyAccountTreeView::columnToggled); // restore the headers const auto grp = KSharedConfig::openConfig()->group(d->getConfGrpName(view)); const auto columnNames = grp.readEntry("HeaderState", QByteArray()); header()->restoreState(columnNames); return d->m_model; } void KMyMoneyAccountTreeView::mouseDoubleClickEvent(QMouseEvent *event) { Q_D(KMyMoneyAccountTreeView); d->openIndex(currentIndex()); event->accept(); } void KMyMoneyAccountTreeView::keyPressEvent(QKeyEvent *event) { Q_D(KMyMoneyAccountTreeView); if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { d->openIndex(currentIndex()); event->accept(); } else { QTreeView::keyPressEvent(event); } } void KMyMoneyAccountTreeView::customContextMenuRequested(const QPoint) { const auto index = model()->index(currentIndex().row(), (int)eAccountsModel::Column::Account, currentIndex().parent()); if (index.isValid() && (model()->flags(index) & Qt::ItemIsSelectable)) { const auto data = model()->data(index, (int)eAccountsModel::Role::Account); if (data.isValid()) { if (data.canConvert()) { emit selectObject(data.value()); emit openContextMenu(data.value()); } if (data.canConvert()) { emit selectObject(data.value()); emit openContextMenu(data.value()); } } } } void KMyMoneyAccountTreeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QTreeView::selectionChanged(selected, deselected); if (!selected.empty()) { auto indexes = selected.front().indexes(); if (!indexes.empty()) { const auto data = model()->data(model()->index(indexes.front().row(), (int)eAccountsModel::Column::Account, indexes.front().parent()), (int)eAccountsModel::Role::Account); if (data.isValid()) { if (data.canConvert()) { emit selectObject(data.value()); } if (data.canConvert()) { emit selectObject(data.value()); } // an object was successfully selected return; } } } // since no object was selected reset the object selection emit selectObject(MyMoneyAccount()); emit selectObject(MyMoneyInstitution()); } diff --git a/kmymoney/widgets/kmymoneycurrencyselector.cpp b/kmymoney/widgets/kmymoneycurrencyselector.cpp index bc71a814d..9327be569 100644 --- a/kmymoney/widgets/kmymoneycurrencyselector.cpp +++ b/kmymoney/widgets/kmymoneycurrencyselector.cpp @@ -1,215 +1,215 @@ /*************************************************************************** kmymoneycurrencyselector.cpp - description ------------------- begin : Tue Apr 6 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Ł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. * * * ***************************************************************************/ #include "kmymoneycurrencyselector.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "icons/icons.h" using namespace Icons; class KMyMoneySecuritySelectorPrivate { Q_DISABLE_COPY(KMyMoneySecuritySelectorPrivate) Q_DECLARE_PUBLIC(KMyMoneySecuritySelector) public: enum displayItemE { Symbol = 0, FullName }; enum displayTypeE { TypeCurrencies = 0x01, TypeSecurities = 0x02, TypeAll = 0x03 }; - KMyMoneySecuritySelectorPrivate(KMyMoneySecuritySelector *qq): + explicit KMyMoneySecuritySelectorPrivate(KMyMoneySecuritySelector *qq): q_ptr(qq), m_displayItem(FullName), m_selectedItemId(0), m_displayOnly(false), m_displayType(TypeAll) { } void selectDisplayItem(displayItemE item) { Q_Q(KMyMoneySecuritySelector); m_displayItem = item; q->update(QString()); } void setDisplayType(displayTypeE type) { m_displayType = type; } KMyMoneySecuritySelector *q_ptr; MyMoneySecurity m_currency; displayItemE m_displayItem; int m_selectedItemId; bool m_displayOnly; displayTypeE m_displayType; QList m_list; }; KMyMoneySecuritySelector::KMyMoneySecuritySelector(QWidget *parent) : KComboBox(parent), d_ptr(new KMyMoneySecuritySelectorPrivate(this)) { // update(QString()); } KMyMoneySecuritySelector::~KMyMoneySecuritySelector() { Q_D(KMyMoneySecuritySelector); delete d; } void KMyMoneySecuritySelector::update(const QString& id) { Q_D(KMyMoneySecuritySelector); MyMoneySecurity curr = MyMoneyFile::instance()->baseCurrency(); QString baseCurrency = curr.id(); if (!id.isEmpty()) curr = d->m_currency; this->clear(); d->m_list.clear(); if (d->m_displayType & KMyMoneySecuritySelectorPrivate::TypeCurrencies) d->m_list += MyMoneyFile::instance()->currencyList(); if (d->m_displayType & KMyMoneySecuritySelectorPrivate::TypeSecurities) d->m_list += MyMoneyFile::instance()->securityList(); // sort qSort(d->m_list); QList::ConstIterator it; // construct a transparent 16x16 pixmap static unsigned char empty_png[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0xF3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xAE, 0xCE, 0x1C, 0xE9, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x0B, 0x13, 0x01, 0x00, 0x9A, 0x9C, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4D, 0x45, 0x07, 0xDB, 0x07, 0x08, 0x0B, 0x16, 0x09, 0xAA, 0xA8, 0x50, 0x21, 0x00, 0x00, 0x00, 0x12, 0x49, 0x44, 0x41, 0x54, 0x38, 0xCB, 0x63, 0x60, 0x18, 0x05, 0xA3, 0x60, 0x14, 0x8C, 0x02, 0x08, 0x00, 0x00, 0x04, 0x10, 0x00, 0x01, 0x85, 0x3F, 0xAA, 0x72, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; QPixmap empty; empty.loadFromData(empty_png, sizeof(empty_png), 0, Qt::AutoColor); QIcon emptyIcon(empty); int itemId = 0; int m_selectedItemId = 0; for (it = d->m_list.constBegin(); it != d->m_list.constEnd(); ++it) { QString display; switch (d->m_displayItem) { default: case KMyMoneySecuritySelectorPrivate::FullName: if ((*it).isCurrency()) { display = QString("%2 (%1)").arg((*it).id()).arg((*it).name()); } else display = QString("%2 (%1)").arg((*it).tradingSymbol()).arg((*it).name()); break; break; case KMyMoneySecuritySelectorPrivate::Symbol: if ((*it).isCurrency()) display = (*it).id(); else display = (*it).tradingSymbol(); break; } if ((*it).id() == baseCurrency) { insertItem(itemId, QIcon::fromTheme(g_Icons[Icon::ViewBankAccount]), display); } else { insertItem(itemId, emptyIcon, display); } if (curr.id() == (*it).id()) { m_selectedItemId = itemId; d->m_currency = (*it); } itemId++; } setCurrentIndex(m_selectedItemId); } const MyMoneySecurity& KMyMoneySecuritySelector::security() const { Q_D(const KMyMoneySecuritySelector); int index = currentIndex(); if ((0 <= index) && (index < d->m_list.size())) return d->m_list[index]; else return d->m_currency; } void KMyMoneySecuritySelector::setSecurity(const MyMoneySecurity& currency) { Q_D(KMyMoneySecuritySelector); d->m_currency = currency; update(QString("x")); } KMyMoneyCurrencySelector::KMyMoneyCurrencySelector(QWidget *parent) : KMyMoneySecuritySelector(parent) { Q_D(KMyMoneySecuritySelector); d->setDisplayType(KMyMoneySecuritySelectorPrivate::TypeCurrencies); } KMyMoneyCurrencySelector::~KMyMoneyCurrencySelector() { } diff --git a/kmymoney/widgets/kmymoneyedit.cpp b/kmymoney/widgets/kmymoneyedit.cpp index fd576f5fc..5f7df3397 100644 --- a/kmymoney/widgets/kmymoneyedit.cpp +++ b/kmymoney/widgets/kmymoneyedit.cpp @@ -1,573 +1,573 @@ /*************************************************************************** kmymoneyedit.cpp ------------------- copyright : (C) 2000 by Michael Edwardes 2004 by Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #include "kmymoneyedit.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneymoneyvalidator.h" #include "kmymoneylineedit.h" #include "kmymoneycalculator.h" #include "mymoneymoney.h" #include "mymoneysecurity.h" #include "icons.h" using namespace Icons; // converted image from kde3.5.1/share/apps/kdevdesignerpart/pics/designer_resetproperty.png static const uchar resetButtonImage[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x08, 0x06, 0x00, 0x00, 0x00, 0x0F, 0x0E, 0x84, 0x76, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4B, 0x47, 0x44, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xA0, 0xBD, 0xA7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x0B, 0x13, 0x01, 0x00, 0x9A, 0x9C, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4D, 0x45, 0x07, 0xD6, 0x06, 0x10, 0x09, 0x36, 0x0C, 0x58, 0x91, 0x11, 0x7C, 0x00, 0x00, 0x00, 0x64, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA, 0x65, 0xC9, 0xA1, 0x0D, 0x02, 0x41, 0x18, 0x84, 0xD1, 0xF7, 0x5F, 0x13, 0x04, 0x9A, 0x39, 0x43, 0x68, 0x81, 0x02, 0x10, 0xB8, 0x13, 0x74, 0x80, 0xC1, 0x21, 0x76, 0x1D, 0xDD, 0xD0, 0x01, 0x65, 0x10, 0x34, 0x9A, 0x0C, 0x66, 0x83, 0x61, 0x92, 0x2F, 0x23, 0x5E, 0x25, 0x01, 0xBD, 0x6A, 0xC6, 0x1D, 0x9B, 0x25, 0x79, 0xC2, 0x34, 0xE0, 0x30, 0x00, 0x56, 0xBD, 0x6A, 0x0D, 0xD5, 0x38, 0xE1, 0xEA, 0x7F, 0xE7, 0x4A, 0xA2, 0x57, 0x1D, 0x71, 0xC1, 0x07, 0xBB, 0x81, 0x8F, 0x09, 0x96, 0xE4, 0x86, 0x3D, 0xDE, 0x78, 0x8D, 0x48, 0xF2, 0xAB, 0xB1, 0x1D, 0x9F, 0xC6, 0xFC, 0x05, 0x46, 0x68, 0x28, 0x6B, 0x58, 0xEE, 0x72, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; class KMyMoneyEditPrivate { Q_DISABLE_COPY(KMyMoneyEditPrivate) Q_DECLARE_PUBLIC(KMyMoneyEdit) public: - KMyMoneyEditPrivate(KMyMoneyEdit *qq) : + explicit KMyMoneyEditPrivate(KMyMoneyEdit *qq) : q_ptr(qq) { } ~KMyMoneyEditPrivate() { } void init() { Q_Q(KMyMoneyEdit); QHBoxLayout *editLayout = new QHBoxLayout(q); editLayout->setSpacing(0); editLayout->setContentsMargins(0, 0, 0, 0); allowEmpty = false; m_edit = new KMyMoneyLineEdit(q, true); m_edit->installEventFilter(q); q->setFocusProxy(m_edit); editLayout->addWidget(m_edit); // Yes, just a simple double validator ! KMyMoneyMoneyValidator *validator = new KMyMoneyMoneyValidator(q); m_edit->setValidator(validator); m_edit->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_calculatorFrame = new QWidget; QVBoxLayout *calculatorFrameVBoxLayout = new QVBoxLayout(m_calculatorFrame); calculatorFrameVBoxLayout->setMargin(0); m_calculatorFrame->setWindowFlags(Qt::Popup); m_calculator = new KMyMoneyCalculator(m_calculatorFrame); calculatorFrameVBoxLayout->addWidget(m_calculator); m_calculatorFrame->hide(); m_calcButton = new QPushButton(QIcon::fromTheme(g_Icons[Icon::AccessoriesCalculator]), QString(), q); m_calcButton->setFocusProxy(m_edit); editLayout->addWidget(m_calcButton); QPixmap pixmap; pixmap.loadFromData(resetButtonImage, sizeof(resetButtonImage), "PNG", 0); m_resetButton = new QPushButton(pixmap, QString(QString()), q); m_resetButton->setEnabled(false); m_resetButton->setFocusProxy(m_edit); editLayout->addWidget(m_resetButton); KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp = kconfig->group("General Options"); if (grp.readEntry("DontShowCalculatorButton", false) == true) q->setCalculatorButtonVisible(false); q->connect(m_edit, &QLineEdit::textChanged, q, &KMyMoneyEdit::theTextChanged); q->connect(m_calculator, &KMyMoneyCalculator::signalResultAvailable, q, &KMyMoneyEdit::slotCalculatorResult); q->connect(m_calcButton, &QAbstractButton::clicked, q, &KMyMoneyEdit::slotCalculatorOpen); q->connect(m_resetButton, &QAbstractButton::clicked, q, &KMyMoneyEdit::resetText); } /** * This method ensures that the text version contains a * fractional part. */ void ensureFractionalPart() { QString s(m_edit->text()); ensureFractionalPart(s); // by setting the text only when it's different then the one that it is already there // we preserve the edit widget's state (like the selection for example) during a // call to ensureFractionalPart() that does not change anything if (s != m_edit->text()) m_edit->setText(s); } /** * Internal helper function for value() and ensureFractionalPart(). */ void ensureFractionalPart(QString& s) const { QString decimalSymbol = QLocale().decimalPoint(); if (decimalSymbol.isEmpty()) decimalSymbol = '.'; // If text contains no 'monetaryDecimalSymbol' then add it // followed by the required number of 0s if (!s.isEmpty()) { if (m_prec > 0) { if (!s.contains(decimalSymbol)) { s += decimalSymbol; for (auto i = 0; i < m_prec; ++i) s += '0'; } } else if (m_prec == 0) { while (s.contains(decimalSymbol)) { int pos = s.lastIndexOf(decimalSymbol); if (pos != -1) { s.truncate(pos); } } } else if (s.contains(decimalSymbol)) { // m_prec == -1 && fraction // no trailing zeroes while (s.endsWith('0')) { s.truncate(s.length() - 1); } // no trailing decimalSymbol if (s.endsWith(decimalSymbol)) s.truncate(s.length() - 1); } } } /** * This method opens the calculator and replays the key * event pointed to by @p ev. If @p ev is 0, then no key * event is replayed. * * @param ev pointer to QKeyEvent that started the calculator. */ void calculatorOpen(QKeyEvent* k) { Q_Q(KMyMoneyEdit); m_calculator->setInitialValues(m_edit->text(), k); int h = m_calculatorFrame->height(); int w = m_calculatorFrame->width(); // usually, the calculator widget is shown underneath the MoneyEdit widget // if it does not fit on the screen, we show it above this widget QPoint p = q->mapToGlobal(QPoint(0, 0)); if (p.y() + q->height() + h > QApplication::desktop()->height()) p.setY(p.y() - h); else p.setY(p.y() + q->height()); // usually, it is shown left aligned. If it does not fit, we align it // to the right edge of the widget if (p.x() + w > QApplication::desktop()->width()) p.setX(p.x() + q->width() - w); QRect r = m_calculator->geometry(); r.moveTopLeft(p); m_calculatorFrame->setGeometry(r); m_calculatorFrame->show(); m_calculator->setFocus(); } KMyMoneyEdit *q_ptr; QString previousText; // keep track of what has been typed QString m_text; // keep track of what was the original value KMyMoneyCalculator* m_calculator; QWidget* m_calculatorFrame; KMyMoneyLineEdit* m_edit; QPushButton* m_calcButton; QPushButton* m_resetButton; int m_prec; bool allowEmpty; /** * This holds the number of precision to be used * when no other information (e.g. from account) * is available. * * @sa setStandardPrecision() */ static int standardPrecision; }; int KMyMoneyEditPrivate::standardPrecision = 2; KMyMoneyEdit::KMyMoneyEdit(QWidget *parent, const int prec) : QWidget(parent), d_ptr(new KMyMoneyEditPrivate(this)) { Q_D(KMyMoneyEdit); d->m_prec = prec; if (prec < -1 || prec > 20) d->m_prec = KMyMoneyEditPrivate::standardPrecision; d->init(); } KMyMoneyEdit::KMyMoneyEdit(const MyMoneySecurity& sec, QWidget *parent) : QWidget(parent), d_ptr(new KMyMoneyEditPrivate(this)) { Q_D(KMyMoneyEdit); d->m_prec = MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()); d->init(); } void KMyMoneyEdit::setStandardPrecision(int prec) { if (prec >= 0 && prec < 20) { KMyMoneyEditPrivate::standardPrecision = prec; } } void KMyMoneyEdit::setValidator(const QValidator* v) { Q_D(KMyMoneyEdit); d->m_edit->setValidator(v); } KMyMoneyEdit::~KMyMoneyEdit() { Q_D(KMyMoneyEdit); delete d; } KLineEdit* KMyMoneyEdit::lineedit() const { Q_D(const KMyMoneyEdit); return d->m_edit; } QString KMyMoneyEdit::text() const { return value().toString(); } void KMyMoneyEdit::setMinimumWidth(int w) { Q_D(KMyMoneyEdit); d->m_edit->setMinimumWidth(w); } void KMyMoneyEdit::setPrecision(const int prec) { Q_D(KMyMoneyEdit); if (prec >= -1 && prec <= 20) { if (prec != d->m_prec) { d->m_prec = prec; // update current display setValue(value()); } } } int KMyMoneyEdit::precision() const { Q_D(const KMyMoneyEdit); return d->m_prec; } bool KMyMoneyEdit::isValid() const { Q_D(const KMyMoneyEdit); return !(d->m_edit->text().isEmpty()); } MyMoneyMoney KMyMoneyEdit::value() const { Q_D(const KMyMoneyEdit); auto txt = d->m_edit->text(); d->ensureFractionalPart(txt); MyMoneyMoney money(txt); if (d->m_prec != -1) money = money.convert(MyMoneyMoney::precToDenom(d->m_prec)); return money; } void KMyMoneyEdit::setValue(const MyMoneyMoney& value) { Q_D(KMyMoneyEdit); // load the value into the widget but don't use thousandsSeparators auto txt = value.formatMoney(QString(), d->m_prec, false); loadText(txt); } void KMyMoneyEdit::loadText(const QString& txt) { Q_D(KMyMoneyEdit); d->m_edit->setText(txt); if (isEnabled() && !txt.isEmpty()) d->ensureFractionalPart(); d->m_text = d->m_edit->text(); d->m_resetButton->setEnabled(false); } void KMyMoneyEdit::clearText() { Q_D(KMyMoneyEdit); d->m_text.clear(); d->m_edit->setText(d->m_text); } void KMyMoneyEdit::setText(const QString& txt) { setValue(MyMoneyMoney(txt)); } void KMyMoneyEdit::resetText() { Q_D(KMyMoneyEdit); d->m_edit->setText(d->m_text); d->m_resetButton->setEnabled(false); } void KMyMoneyEdit::theTextChanged(const QString & theText) { Q_D(KMyMoneyEdit); QString txt = QLocale().decimalPoint(); QString l_text = theText; QString nsign, psign; #if 0 KLocale * l = KLocale::global(); if (l->negativeMonetarySignPosition() == KLocale::ParensAround || l->positiveMonetarySignPosition() == KLocale::ParensAround) { nsign = psign = '('; } else { nsign = l->negativeSign(); psign = l->positiveSign(); } #else nsign = "-"; psign = QString(); #endif auto i = 0; if (isEnabled()) { QValidator::State state = d->m_edit->validator()->validate(l_text, i); if (state == QValidator::Intermediate) { if (l_text.length() == 1) { if (l_text != txt && l_text != nsign && l_text != psign) state = QValidator::Invalid; } } if (state == QValidator::Invalid) d->m_edit->setText(d->previousText); else { d->previousText = l_text; emit textChanged(d->m_edit->text()); d->m_resetButton->setEnabled(true); } } } bool KMyMoneyEdit::eventFilter(QObject * /* o */ , QEvent *event) { Q_D(KMyMoneyEdit); auto rc = false; // we want to catch some keys that are usually handled by // the base class (e.g. '+', '-', etc.) if (event->type() == QEvent::KeyPress) { QKeyEvent *k = static_cast(event); rc = true; switch (k->key()) { case Qt::Key_Plus: case Qt::Key_Minus: if (d->m_edit->hasSelectedText()) { d->m_edit->cut(); } if (d->m_edit->text().length() == 0) { rc = false; break; } // in case of '-' we do not enter the calculator when // the current position is the beginning and there is // no '-' sign at the first position. if (k->key() == Qt::Key_Minus) { if (d->m_edit->cursorPosition() == 0 && d->m_edit->text()[0] != '-') { rc = false; break; } } // intentional fall through case Qt::Key_Slash: case Qt::Key_Asterisk: case Qt::Key_Percent: if (d->m_edit->hasSelectedText()) { // remove the selected text d->m_edit->cut(); } d->calculatorOpen(k); break; default: rc = false; break; } } else if (event->type() == QEvent::FocusOut) { if (!d->m_edit->text().isEmpty() || !d->allowEmpty) d->ensureFractionalPart(); if (MyMoneyMoney(d->m_edit->text()) != MyMoneyMoney(d->m_text) && !d->m_calculator->isVisible()) { emit valueChanged(d->m_edit->text()); } d->m_text = d->m_edit->text(); } return rc; } void KMyMoneyEdit::slotCalculatorOpen() { Q_D(KMyMoneyEdit); d->calculatorOpen(0); } void KMyMoneyEdit::slotCalculatorResult() { Q_D(KMyMoneyEdit); QString result; if (d->m_calculator != 0) { d->m_calculatorFrame->hide(); d->m_edit->setText(d->m_calculator->result()); d->ensureFractionalPart(); emit valueChanged(d->m_edit->text()); d->m_text = d->m_edit->text(); } } QWidget* KMyMoneyEdit::focusWidget() const { Q_D(const KMyMoneyEdit); QWidget* w = d->m_edit; while (w->focusProxy()) w = w->focusProxy(); return w; } void KMyMoneyEdit::setCalculatorButtonVisible(const bool show) { Q_D(KMyMoneyEdit); d->m_calcButton->setVisible(show); } void KMyMoneyEdit::setResetButtonVisible(const bool show) { Q_D(KMyMoneyEdit); d->m_resetButton->setVisible(show); } void KMyMoneyEdit::setAllowEmpty(bool allowed) { Q_D(KMyMoneyEdit); d->allowEmpty = allowed; } bool KMyMoneyEdit::isCalculatorButtonVisible() const { Q_D(const KMyMoneyEdit); return d->m_calcButton->isVisible(); } bool KMyMoneyEdit::isResetButtonVisible() const { Q_D(const KMyMoneyEdit); return d->m_resetButton->isVisible(); } bool KMyMoneyEdit::isEmptyAllowed() const { Q_D(const KMyMoneyEdit); return d->allowEmpty; } void KMyMoneyEdit::setPlaceholderText(const QString& hint) const { Q_D(const KMyMoneyEdit); if (d->m_edit) d->m_edit->setPlaceholderText(hint); } bool KMyMoneyEdit::isReadOnly() const { Q_D(const KMyMoneyEdit); if (d->m_edit) return d->m_edit->isReadOnly(); return false; } void KMyMoneyEdit::setReadOnly(bool readOnly) { Q_D(KMyMoneyEdit); // we use the QLineEdit::setReadOnly() method directly to avoid // changing the background between readonly and read/write mode // as it is done by the KLineEdit code. if (d->m_edit) d->m_edit->QLineEdit::setReadOnly(readOnly); //krazy:exclude=qclasses } diff --git a/kmymoney/widgets/kmymoneyselector_p.h b/kmymoney/widgets/kmymoneyselector_p.h index 079d33973..dcba92c41 100644 --- a/kmymoney/widgets/kmymoneyselector_p.h +++ b/kmymoney/widgets/kmymoneyselector_p.h @@ -1,93 +1,92 @@ /*************************************************************************** kmymoneyselector_p.h ------------------- begin : Thu Jun 29 2006 copyright : (C) 2006 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KMYMONEYSELECTOR_P_H #define KMYMONEYSELECTOR_P_H #include "kmymoneyselector.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class QHBoxLayout; class KMyMoneySelectorPrivate { Q_DISABLE_COPY(KMyMoneySelectorPrivate) Q_DECLARE_PUBLIC(KMyMoneySelector) public: - - KMyMoneySelectorPrivate(KMyMoneySelector *qq) : + explicit KMyMoneySelectorPrivate(KMyMoneySelector *qq) : q_ptr(qq), m_treeWidget(nullptr), m_layout(nullptr) { } void init() { Q_Q(KMyMoneySelector); q->setAutoFillBackground(true); m_selMode = QTreeWidget::SingleSelection; m_treeWidget = new QTreeWidget(q); // don't show horizontal scroll bar m_treeWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_treeWidget->setSortingEnabled(false); m_treeWidget->setAlternatingRowColors(true); m_treeWidget->setAllColumnsShowFocus(true); m_layout = new QHBoxLayout(q); m_layout->setSpacing(0); m_layout->setMargin(0); m_treeWidget->header()->hide(); m_layout->addWidget(m_treeWidget); // force init m_selMode = QTreeWidget::MultiSelection; q->setSelectionMode(QTreeWidget::SingleSelection); q->connect(m_treeWidget, &QTreeWidget::itemPressed, q, &KMyMoneySelector::slotItemPressed); q->connect(m_treeWidget, &QTreeWidget::itemChanged, q, &KMyMoneySelector::stateChanged); } KMyMoneySelector *q_ptr; QTreeWidget* m_treeWidget; QStringList m_itemList; QString m_baseName; QTreeWidget::SelectionMode m_selMode; QHBoxLayout* m_layout; }; #endif diff --git a/kmymoney/widgets/kmymoneytextedit.cpp b/kmymoney/widgets/kmymoneytextedit.cpp index 3974bac71..5fab79af2 100644 --- a/kmymoney/widgets/kmymoneytextedit.cpp +++ b/kmymoney/widgets/kmymoneytextedit.cpp @@ -1,192 +1,192 @@ /* This file is part of KMyMoney, A Personal Finance Manager by KDE Copyright (C) 2013 Christian Dávid (C) 2017 by Ł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 "kmymoneytextedit.h" #include "kmymoneytextedithighlighter.h" #include class KMyMoneyTextEditPrivate { Q_DISABLE_COPY(KMyMoneyTextEditPrivate) Q_DECLARE_PUBLIC(KMyMoneyTextEdit) public: - KMyMoneyTextEditPrivate(KMyMoneyTextEdit *qq) : + explicit KMyMoneyTextEditPrivate(KMyMoneyTextEdit *qq) : q_ptr(qq), m_maxLength(-1), m_maxLineLength(-1), m_maxLines(-1), m_allowedChars(QString(QString())), m_highligther(0) { } bool isEventAllowed(QKeyEvent* e) const { Q_Q(const KMyMoneyTextEdit); const QString text = e->text(); if (!text.isEmpty()) { if (text.at(0).isPrint()) { if (!m_allowedChars.contains(text)) return false; // Do not check max lengths etc if something is replaced if (q->textCursor().hasSelection()) return true; const QString plainText = q->toPlainText(); if (m_maxLength != -1 && plainText.length() >= m_maxLength) return false; if (m_maxLineLength != -1 && q->textCursor().block().length() - 1 >= m_maxLineLength) return false; } else if (m_maxLines != -1 && text.at(0) == '\r' && q->toPlainText().count('\n') + 1 >= m_maxLines) { // Does this work on non-linux OSes as well? return false; } } return true; } KMyMoneyTextEdit *q_ptr; int m_maxLength; int m_maxLineLength; int m_maxLines; QString m_allowedChars; KMyMoneyTextEditHighlighter* m_highligther; }; void KMyMoneyTextEdit::setReadOnly(bool readOnly) { KTextEdit::setReadOnly(readOnly); } /* KMyMoneyTextEdit */ KMyMoneyTextEdit::KMyMoneyTextEdit(QWidget* parent) : KTextEdit(parent), d_ptr(new KMyMoneyTextEditPrivate(this)) { Q_D(KMyMoneyTextEdit); setWordWrapMode(QTextOption::ManualWrap); d->m_highligther = new KMyMoneyTextEditHighlighter(this); } KMyMoneyTextEdit::~KMyMoneyTextEdit() { Q_D(KMyMoneyTextEdit); delete d; } bool KMyMoneyTextEdit::isValid() const { Q_D(const KMyMoneyTextEdit); const QString text = toPlainText(); if (d->m_maxLength != -1 && text.length() >= d->m_maxLength) return false; const QStringList lines = text.split('\n'); if (d->m_maxLines != -1 && lines.count() >= d->m_maxLines) { return false; } if (d->m_maxLineLength != -1) { foreach (QString line, lines) { if (line.length() > d->m_maxLineLength) return false; } } const int length = text.length(); for (auto i = 0; i < length; ++i) { if (!d->m_allowedChars.contains(text.at(i))) return false; } return true; } void KMyMoneyTextEdit::keyReleaseEvent(QKeyEvent* e) { Q_D(KMyMoneyTextEdit); if (d->isEventAllowed(e)) KTextEdit::keyReleaseEvent(e); } void KMyMoneyTextEdit::keyPressEvent(QKeyEvent* e) { Q_D(KMyMoneyTextEdit); if (d->isEventAllowed(e)) KTextEdit::keyPressEvent(e); } int KMyMoneyTextEdit::maxLength() const { Q_D(const KMyMoneyTextEdit); return d->m_maxLength; } void KMyMoneyTextEdit::setMaxLength(const int& maxLength) { Q_D(KMyMoneyTextEdit); d->m_maxLength = maxLength; d->m_highligther->setMaxLength(d->m_maxLength); } int KMyMoneyTextEdit::maxLineLength() const { Q_D(const KMyMoneyTextEdit); return d->m_maxLineLength; } void KMyMoneyTextEdit::setMaxLineLength(const int& maxLineLength) { Q_D(KMyMoneyTextEdit); d->m_maxLineLength = maxLineLength; d->m_highligther->setMaxLineLength(maxLineLength); } int KMyMoneyTextEdit::maxLines() const { Q_D(const KMyMoneyTextEdit); return d->m_maxLines; } void KMyMoneyTextEdit::setMaxLines(const int& maxLines) { Q_D(KMyMoneyTextEdit); d->m_maxLines = maxLines; d->m_highligther->setMaxLines(maxLines); } QString KMyMoneyTextEdit::allowedChars() const { Q_D(const KMyMoneyTextEdit); return d->m_allowedChars; } void KMyMoneyTextEdit::setAllowedChars(const QString& allowedChars) { Q_D(KMyMoneyTextEdit); d->m_allowedChars = allowedChars; d->m_highligther->setAllowedChars(allowedChars); } diff --git a/kmymoney/widgets/styleditemdelegateforwarder.h b/kmymoney/widgets/styleditemdelegateforwarder.h index 2babcea24..64efd428a 100644 --- a/kmymoney/widgets/styleditemdelegateforwarder.h +++ b/kmymoney/widgets/styleditemdelegateforwarder.h @@ -1,66 +1,66 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * 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 STYLEDITEMDELEGATEFORWARDER_H #define STYLEDITEMDELEGATEFORWARDER_H #include #include "kmm_widgets_export.h" /** * @brief Helper to use multiple item delegates in a view * * This class allows to select the used item delegate based on the QModelIndex. * */ class KMM_WIDGETS_EXPORT StyledItemDelegateForwarder : public QAbstractItemDelegate { Q_OBJECT public: - StyledItemDelegateForwarder(QObject* parent = 0); + explicit StyledItemDelegateForwarder(QObject* parent = 0); virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual void setEditorData(QWidget* editor, const QModelIndex& index) const; virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; /** * @brief Return delegate for a given index * * If an method of this class is called, it uses this function to receive * the correct delegate where the call is forwarded to. * * @return You must return a valid item delegate. * @see connectSignals() */ virtual QAbstractItemDelegate* getItemDelegate(const QModelIndex& index) const = 0; protected: /** * @brief Connects all signals accordingly * * Call this function if you create a new delegate in getItemDelegate(). */ void connectSignals(QAbstractItemDelegate* delegate, Qt::ConnectionType type = Qt::AutoConnection) const; }; #endif // STYLEDITEMDELEGATEFORWARDER_H diff --git a/kmymoney/wizards/kmymoneywizard_p.h b/kmymoney/wizards/kmymoneywizard_p.h index be725b067..1a7da163a 100644 --- a/kmymoney/wizards/kmymoneywizard_p.h +++ b/kmymoney/wizards/kmymoneywizard_p.h @@ -1,318 +1,318 @@ /*************************************************************************** kmymoneywizard_p.h ------------------- copyright : (C) 2006 by Thomas Baumagrt email : ipwizard@users.sourceforge.net (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KMYMONEYWIZARD_P_H #define KMYMONEYWIZARD_P_H #include "kmymoneywizard.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneywizardpage.h" #include "kmymoneywizardpage_p.h" #include "kmymoneytitlelabel.h" #include "icons/icons.h" using namespace Icons; class KMyMoneyWizardPrivate { Q_DISABLE_COPY(KMyMoneyWizardPrivate) Q_DECLARE_PUBLIC(KMyMoneyWizard) public: - KMyMoneyWizardPrivate(KMyMoneyWizard *qq) : + explicit KMyMoneyWizardPrivate(KMyMoneyWizard *qq) : q_ptr(qq), m_step(0) { } virtual ~KMyMoneyWizardPrivate() { } void init(bool modal) { Q_Q(KMyMoneyWizard); q->setModal(modal); // enable the little grip in the right corner q->setSizeGripEnabled(true); // create buttons m_cancelButton = new QPushButton(i18n("&Cancel"), q); m_backButton = new QPushButton(i18nc("Go to previous page of the wizard", "&Back"), q); m_nextButton = new QPushButton(i18nc("Go to next page of the wizard", "&Next"), q); m_finishButton = new QPushButton(i18nc("Finish the wizard", "&Finish"), q); m_helpButton = new QPushButton(i18n("&Help"), q); if (q->style()->styleHint(QStyle::SH_DialogButtonBox_ButtonsHaveIcons, 0, q)) { m_backButton->setIcon(KStandardGuiItem::back(KStandardGuiItem::UseRTL).icon()); m_nextButton->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon()); m_finishButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogOKApply])); m_cancelButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogCancel])); m_helpButton->setIcon(QIcon::fromTheme(g_Icons[Icon::HelpContents])); } // create button layout m_buttonLayout = new QHBoxLayout; m_buttonLayout->addWidget(m_helpButton); m_buttonLayout->addStretch(1); m_buttonLayout->addWidget(m_backButton); m_buttonLayout->addWidget(m_nextButton); m_buttonLayout->addWidget(m_finishButton); m_buttonLayout->addWidget(m_cancelButton); // create wizard layout m_wizardLayout = new QVBoxLayout(q); m_wizardLayout->setContentsMargins(6, 6, 6, 6); m_wizardLayout->setSpacing(0); m_wizardLayout->setObjectName("wizardLayout"); m_titleLabel = new KMyMoneyTitleLabel(q); m_titleLabel->setObjectName("titleLabel"); m_wizardLayout->addWidget(m_titleLabel); QHBoxLayout* hboxLayout = new QHBoxLayout; hboxLayout->setContentsMargins(0, 0, 0, 0); hboxLayout->setSpacing(6); hboxLayout->setObjectName("hboxLayout"); // create stage layout and frame m_stepFrame = new QFrame(q); m_stepFrame->setObjectName("stepFrame"); QPalette palette = m_stepFrame->palette(); palette.setColor(m_stepFrame->backgroundRole(), KColorScheme::NormalText); m_stepFrame->setPalette(palette); m_stepLayout = new QVBoxLayout(m_stepFrame); m_stepLayout->setContentsMargins(11, 11, 11, 11); m_stepLayout->setSpacing(6); m_stepLayout->setObjectName("stepLayout"); m_stepLayout->addWidget(new QLabel(QString(), m_stepFrame)); m_stepLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding)); m_stepLabel = new QLabel(m_stepFrame); m_stepLabel->setAlignment(Qt::AlignHCenter); m_stepLayout->addWidget(m_stepLabel); hboxLayout->addWidget(m_stepFrame); m_stepPalette = m_stepLabel->palette(); // add a vertical line between the stepFrame and the pages QFrame* line = new QFrame(q); line->setObjectName("line"); line->setFrameShadow(QFrame::Sunken); line->setFrameShape(QFrame::VLine); hboxLayout->addWidget(line); // create page layout m_pageLayout = new QVBoxLayout; m_pageLayout->setContentsMargins(0, 0, 0, 0); m_pageLayout->setSpacing(6); m_pageLayout->setObjectName("pageLayout"); // the page will be inserted later dynamically above q line line = new QFrame(q); line->setObjectName("line"); line->setFrameShadow(QFrame::Sunken); line->setFrameShape(QFrame::HLine); m_pageLayout->addWidget(line); m_pageLayout->addLayout(m_buttonLayout); // now glue everything together hboxLayout->addLayout(m_pageLayout); m_wizardLayout->addLayout(hboxLayout); q->resize(QSize(670, 550).expandedTo(q->minimumSizeHint())); m_titleLabel->setText(i18n("No Title specified")); m_titleLabel->setRightImageFile("pics/titlelabel_background.png"); m_finishButton->hide(); q->connect(m_backButton, &QAbstractButton::clicked, q, &KMyMoneyWizard::backButtonClicked); q->connect(m_nextButton, &QAbstractButton::clicked, q, &KMyMoneyWizard::nextButtonClicked); q->connect(m_cancelButton, &QAbstractButton::clicked, q, &QDialog::reject); q->connect(m_finishButton, &QAbstractButton::clicked, q, &KMyMoneyWizard::accept); q->connect(m_helpButton, &QAbstractButton::clicked, q, &KMyMoneyWizard::helpButtonClicked); } /** * Switch to page which is currently the top of the history stack. * @p oldPage is a pointer to the current page or 0 if no page * is shown. * * @param oldPage pointer to currently displayed page */ void switchPage(KMyMoneyWizardPage* oldPage) { Q_Q(KMyMoneyWizard); if (oldPage) { oldPage->widget()->hide(); m_pageLayout->removeWidget(oldPage->widget()); q->disconnect(oldPage->object(), SIGNAL(completeStateChanged()), q, SLOT(completeStateChanged())); } KMyMoneyWizardPage* newPage = m_history.back(); if (newPage) { m_pageLayout->insertWidget(0, newPage->widget()); q->connect(newPage->object(), SIGNAL(completeStateChanged()), q, SLOT(completeStateChanged())); newPage->widget()->show(); selectStep(newPage->step()); if (newPage->isLastPage()) { m_nextButton->setDefault(false); m_finishButton->setDefault(true); } else { m_finishButton->setDefault(false); m_nextButton->setDefault(true); } QWidget* w = newPage->initialFocusWidget(); if (w) w->setFocus(); } q->completeStateChanged(); } /** * This method selects the step given by @p step. * * @param step step to be selected */ void selectStep(int step) { if ((step < 1) || (step > m_steps.count())) return; m_step = step; QList::iterator it_l; QFont f = m_steps[0]->font(); for (it_l = m_steps.begin(); it_l != m_steps.end(); ++it_l) { f.setBold(false); (*it_l)->setFrameStyle(QFrame::NoFrame); if (--step == 0) { f.setBold(true); (*it_l)->setFrameStyle(QFrame::Box | QFrame::Sunken); } (*it_l)->setFont(f); } updateStepCount(); } /** * This method sets up the first page after creation of the object * * @param page pointer to first page of wizard */ void setFirstPage(KMyMoneyWizardPage* page) { page->resetPage(); m_history.clear(); m_history.append(page); switchPage(0); } /** * This method allows to hide or show a @p step. * * @param step step to be shown/hidden * @param hidden hide step if true (the default) or show it if false */ void setStepHidden(int step, bool hidden = true) { if ((step < 1) || (step > m_steps.count())) return; m_steps[--step]->setHidden(hidden); updateStepCount(); } void updateStepCount() { QList::iterator it_l; int stepCount = 0; int hiddenAdjust = 0; int step = 0; for (it_l = m_steps.begin(); it_l != m_steps.end(); ++it_l) { if (!(*it_l)->isHidden()) ++stepCount; else if (step < m_step) hiddenAdjust++; ++step; } m_stepLabel->setText(i18n("Step %1 of %2", (m_step - hiddenAdjust), stepCount)); } KMyMoneyWizard *q_ptr; /* * The buttons */ QPushButton* m_cancelButton; QPushButton* m_backButton; QPushButton* m_nextButton; QPushButton* m_finishButton; QPushButton* m_helpButton; /* * The layouts */ QVBoxLayout* m_wizardLayout; QVBoxLayout* m_stepLayout; QVBoxLayout* m_pageLayout; QHBoxLayout* m_buttonLayout; /* * Some misc. widgets required */ QFrame* m_stepFrame; QLabel* m_stepLabel; QPalette m_stepPalette; QList m_steps; // the list of step labels int m_step; // the currently selected step /* * The title bar */ KMyMoneyTitleLabel* m_titleLabel; /* * The history stack */ QList m_history; QString m_helpContext; }; #endif diff --git a/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp b/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp index 3f87daea3..86731157c 100644 --- a/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp +++ b/kmymoney/wizards/newinvestmentwizard/knewinvestmentwizard.cpp @@ -1,285 +1,284 @@ /*************************************************************************** knewinvestmentwizard - description ------------------- begin : Sat Dec 4 2004 copyright : (C) 2004 by Thomas Baumgart email : kmymoney-devel@kde.org (C) 2017 by Ł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. * * * ***************************************************************************/ #include "knewinvestmentwizard.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_knewinvestmentwizard.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyfile.h" #include "webpricequote.h" #include "kmymoneyutils.h" class KNewInvestmentWizardPrivate { Q_DISABLE_COPY(KNewInvestmentWizardPrivate) Q_DECLARE_PUBLIC(KNewInvestmentWizard) public: - - KNewInvestmentWizardPrivate(KNewInvestmentWizard *qq) : + explicit KNewInvestmentWizardPrivate(KNewInvestmentWizard *qq) : q_ptr(qq), ui(new Ui::KNewInvestmentWizard) { } ~KNewInvestmentWizardPrivate() { delete ui; } void init1() { Q_Q(KNewInvestmentWizard); ui->m_onlineUpdatePage->slotSourceChanged(false); // make sure, the back button does not clear fields q->setOption(QWizard::IndependentPages, true); // enable the help button q->setOption(q->HaveHelpButton, true); q->connect(q, &KNewInvestmentWizard::helpRequested, q, &KNewInvestmentWizard::slotHelp); m_createAccount = true; // Update label in case of edit if (!m_account.id().isEmpty()) { ui->m_investmentTypePage->setIntroLabelText(i18n("This wizard allows you to modify the selected investment.")); } if (!m_security.id().isEmpty()) { ui->m_investmentTypePage->setIntroLabelText(i18n("This wizard allows you to modify the selected security.")); } KMyMoneyUtils::updateWizardButtons(q); } void init2() { ui->m_investmentTypePage->init2(m_security); ui->m_investmentDetailsPage->init2(m_security); ui->m_onlineUpdatePage->init2(m_security); ui->m_onlineUpdatePage->slotCheckPage(m_security.value("kmm-online-source")); } KNewInvestmentWizard *q_ptr; Ui::KNewInvestmentWizard *ui; MyMoneyAccount m_account; MyMoneySecurity m_security; bool m_createAccount; }; KNewInvestmentWizard::KNewInvestmentWizard(QWidget *parent) : QWizard(parent), d_ptr(new KNewInvestmentWizardPrivate(this)) { Q_D(KNewInvestmentWizard); d->ui->setupUi(this); d->init1(); d->ui->m_onlineUpdatePage->slotCheckPage(QString()); d->ui->m_investmentDetailsPage->setupInvestmentSymbol(); connect(d->ui->m_investmentDetailsPage, &KInvestmentDetailsWizardPage::checkForExistingSymbol, this, &KNewInvestmentWizard::slotCheckForExistingSymbol); } KNewInvestmentWizard::KNewInvestmentWizard(const MyMoneyAccount& acc, QWidget *parent) : QWizard(parent), d_ptr(new KNewInvestmentWizardPrivate(this)) { Q_D(KNewInvestmentWizard); d->ui->setupUi(this); d->m_account = acc; setWindowTitle(i18n("Investment detail wizard")); d->init1(); // load the widgets with the data setName(d->m_account.name()); d->m_security = MyMoneyFile::instance()->security(d->m_account.currencyId()); d->init2(); int priceMode = 0; if (!d->m_account.value("priceMode").isEmpty()) priceMode = d->m_account.value("priceMode").toInt(); d->ui->m_investmentDetailsPage->setCurrentPriceMode(priceMode); } KNewInvestmentWizard::KNewInvestmentWizard(const MyMoneySecurity& security, QWidget *parent) : QWizard(parent), d_ptr(new KNewInvestmentWizardPrivate(this)) { Q_D(KNewInvestmentWizard); d->ui->setupUi(this); d->m_security = security; setWindowTitle(i18n("Security detail wizard")); d->init1(); d->m_createAccount = false; // load the widgets with the data setName(security.name()); d->init2(); // no chance to change the price mode here d->ui->m_investmentDetailsPage->setCurrentPriceMode(0); d->ui->m_investmentDetailsPage->setPriceModeEnabled(false); } KNewInvestmentWizard::~KNewInvestmentWizard() { } void KNewInvestmentWizard::setName(const QString& name) { Q_D(KNewInvestmentWizard); d->ui->m_investmentDetailsPage->setName(name); } void KNewInvestmentWizard::slotCheckForExistingSymbol(const QString& symbol) { Q_D(KNewInvestmentWizard); Q_UNUSED(symbol); if (field("investmentName").toString().isEmpty()) { QList list = MyMoneyFile::instance()->securityList(); auto type = static_cast(field("securityType").toInt()); foreach (const MyMoneySecurity& it_s, list) { if (it_s.securityType() == type && it_s.tradingSymbol() == field("investmentSymbol").toString()) { d->m_security = MyMoneySecurity(); if (KMessageBox::questionYesNo(this, i18n("The selected symbol is already on file. Do you want to reuse the existing security?"), i18n("Security found")) == KMessageBox::Yes) { d->m_security = it_s; d->init2(); d->ui->m_investmentDetailsPage->loadName(d->m_security.name()); } break; } } } } void KNewInvestmentWizard::slotHelp() { KHelpClient::invokeHelp("details.investments.newinvestmentwizard"); } void KNewInvestmentWizard::createObjects(const QString& parentId) { Q_D(KNewInvestmentWizard); auto file = MyMoneyFile::instance(); auto type = static_cast(field("securityType").toInt()); auto roundingMethod = static_cast(field("roundingMethod").toInt()); MyMoneyFileTransaction ft; try { // update all relevant attributes only, if we create a stock // account and the security is unknown or we modifiy the security MyMoneySecurity newSecurity(d->m_security); newSecurity.setName(field("investmentName").toString()); newSecurity.setTradingSymbol(field("investmentSymbol").toString()); newSecurity.setTradingMarket(field("tradingMarket").toString()); newSecurity.setSmallestAccountFraction(field("fraction").value().formatMoney("", 0, false).toUInt()); newSecurity.setPricePrecision(MyMoneyMoney(field("pricePrecision").toUInt()).formatMoney("", 0, false).toUInt()); newSecurity.setTradingCurrency(field("tradingCurrencyEdit").value().id()); newSecurity.setSecurityType(type); newSecurity.setRoundingMethod(roundingMethod); newSecurity.deletePair("kmm-online-source"); newSecurity.deletePair("kmm-online-quote-system"); newSecurity.deletePair("kmm-online-factor"); newSecurity.deletePair("kmm-security-id"); if (!field("onlineSourceCombo").toString().isEmpty()) { if (field("useFinanceQuote").toBool()) { FinanceQuoteProcess p; newSecurity.setValue("kmm-online-quote-system", "Finance::Quote"); newSecurity.setValue("kmm-online-source", p.crypticName(field("onlineSourceCombo").toString())); } else { newSecurity.setValue("kmm-online-source", field("onlineSourceCombo").toString()); } } if (d->ui->m_onlineUpdatePage->isOnlineFactorEnabled() && (field("onlineFactor").value() != MyMoneyMoney::ONE)) newSecurity.setValue("kmm-online-factor", field("onlineFactor").value().toString()); if (!field("investmentIdentification").toString().isEmpty()) newSecurity.setValue("kmm-security-id", field("investmentIdentification").toString()); if (d->m_security.id().isEmpty() || newSecurity != d->m_security) { d->m_security = newSecurity; // add or update it if (d->m_security.id().isEmpty()) { file->addSecurity(d->m_security); } else { file->modifySecurity(d->m_security); } } if (d->m_createAccount) { // now that the security exists, we can add the account to store it d->m_account.setName(field("investmentName").toString()); if (d->m_account.accountType() == eMyMoney::Account::Type::Unknown) d->m_account.setAccountType(eMyMoney::Account::Type::Stock); d->m_account.setCurrencyId(d->m_security.id()); switch (d->ui->m_investmentDetailsPage->priceMode()) { case 0: d->m_account.deletePair("priceMode"); break; case 1: case 2: d->m_account.setValue("priceMode", QString("%1").arg(d->ui->m_investmentDetailsPage->priceMode())); break; } // update account's fraction in case its security fraction has changed // otherwise KMM restart is required because this won't happen automatically d->m_account.fraction(d->m_security); if (d->m_account.id().isEmpty()) { MyMoneyAccount parent = file->account(parentId); file->addAccount(d->m_account, parent); } else file->modifyAccount(d->m_account); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to create all objects for the investment"), QString("%1 caugt in %2:%3").arg(e.what()).arg(e.file()).arg(e.line())); } } MyMoneyAccount KNewInvestmentWizard::account() const { Q_D(const KNewInvestmentWizard); return d->m_account; } diff --git a/kmymoney/wizards/newloanwizard/knewloanwizard_p.h b/kmymoney/wizards/newloanwizard/knewloanwizard_p.h index 02753fcf7..27d167746 100644 --- a/kmymoney/wizards/newloanwizard/knewloanwizard_p.h +++ b/kmymoney/wizards/newloanwizard/knewloanwizard_p.h @@ -1,540 +1,540 @@ /*************************************************************************** knewloanwizard_p.cpp - description ------------------- copyright : (C) 2017 by Ł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. * * * ***************************************************************************/ #ifndef KNEWLOANWIZARD_P_H #define KNEWLOANWIZARD_P_H #include "knewloanwizard.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_knewloanwizard.h" #include "ui_namewizardpage.h" #include "ui_firstpaymentwizardpage.h" #include "ui_loanamountwizardpage.h" #include "ui_interestwizardpage.h" #include "ui_paymenteditwizardpage.h" #include "ui_finalpaymentwizardpage.h" #include "ui_interestcategorywizardpage.h" #include "ui_assetaccountwizardpage.h" #include "ui_schedulewizardpage.h" #include "ui_paymentwizardpage.h" #include "kmymoneyutils.h" #include "kmymoneysettings.h" #include "mymoneyfinancialcalculator.h" #include "mymoneyfile.h" #include "mymoneyaccountloan.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" namespace Ui { class KNewLoanWizard; } class KNewLoanWizard; class KNewLoanWizardPrivate { Q_DISABLE_COPY(KNewLoanWizardPrivate) Q_DECLARE_PUBLIC(KNewLoanWizard) public: - KNewLoanWizardPrivate(KNewLoanWizard *qq) : + explicit KNewLoanWizardPrivate(KNewLoanWizard *qq) : q_ptr(qq), ui(new Ui::KNewLoanWizard) { } ~KNewLoanWizardPrivate() { delete ui; } void init() { Q_Q(KNewLoanWizard); ui->setupUi(q); m_pages = QBitArray(KNewLoanWizard::Page_Summary + 1, true); q->setModal(true); KMyMoneyMVCCombo::setSubstringSearchForChildren(ui->m_namePage, !KMyMoneySettings::stringMatchFromStart()); // make sure, the back button does not clear fields q->setOption(QWizard::IndependentPages, true); // connect(m_payeeEdit, SIGNAL(newPayee(QString)), this, SLOT(slotNewPayee(QString))); q->connect(ui->m_namePage->ui->m_payeeEdit, &KMyMoneyMVCCombo::createItem, q, &KNewLoanWizard::createPayee); q->connect(ui->m_additionalFeesPage, &AdditionalFeesWizardPage::newCategory, q, &KNewLoanWizard::newCategory); q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KNewLoanWizard::slotReloadEditWidgets); resetCalculator(); q->slotReloadEditWidgets(); // As default we assume a liability loan, with fixed interest rate, // with a first payment due on the 30th of this month. All payments // should be recorded and none have been made so far. //FIXME: port ui->m_firstPaymentPage->ui->m_firstDueDateEdit->loadDate(QDate(QDate::currentDate().year(), QDate::currentDate().month(), 30)); // FIXME: we currently only support interest calculation on reception m_pages.clearBit(KNewLoanWizard::Page_InterestCalculation); // turn off all pages that are contained here for derived classes m_pages.clearBit(KNewLoanWizard::Page_EditIntro); m_pages.clearBit(KNewLoanWizard::Page_EditSelection); m_pages.clearBit(KNewLoanWizard::Page_EffectiveDate); m_pages.clearBit(KNewLoanWizard::Page_PaymentEdit); m_pages.clearBit(KNewLoanWizard::Page_InterestEdit); m_pages.clearBit(KNewLoanWizard::Page_SummaryEdit); // for now, we don't have online help :-( q->setOption(QWizard::HaveHelpButton, false); // setup a phony transaction for additional fee processing m_account = MyMoneyAccount("Phony-ID", MyMoneyAccount()); m_split.setAccountId(m_account.id()); m_split.setValue(MyMoneyMoney()); m_transaction.addSplit(m_split); KMyMoneyUtils::updateWizardButtons(q); } void resetCalculator() { Q_Q(KNewLoanWizard); ui->m_loanAmountPage->resetCalculator(); ui->m_interestPage->resetCalculator(); ui->m_durationPage->resetCalculator(); ui->m_paymentPage->resetCalculator(); ui->m_finalPaymentPage->resetCalculator(); q->setField("additionalCost", MyMoneyMoney().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())))); } void updateLoanAmount() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("loanAmountEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("loanAmountEdit").value().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); } q->setField("loanAmount1", txt); q->setField("loanAmount2", txt); q->setField("loanAmount3", txt); q->setField("loanAmount4", txt); q->setField("loanAmount5", txt); } void updateInterestRate() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("interestRateEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("interestRateEdit").value().formatMoney(QString(), 3) + QString("%"); } q->setField("interestRate1", txt); q->setField("interestRate2", txt); q->setField("interestRate3", txt); q->setField("interestRate4", txt); q->setField("interestRate5", txt); } void updateDuration() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (q->field("durationValueEdit").toInt() == 0) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = QString().sprintf("%d ", q->field("durationValueEdit").toInt()) + q->field("durationUnitEdit").toString(); } q->setField("duration1", txt); q->setField("duration2", txt); q->setField("duration3", txt); q->setField("duration4", txt); q->setField("duration5", txt); } void updatePayment() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("paymentEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("paymentEdit").value().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); } q->setField("payment1", txt); q->setField("payment2", txt); q->setField("payment3", txt); q->setField("payment4", txt); q->setField("payment5", txt); q->setField("basePayment", txt); } void updateFinalPayment() { Q_Q(KNewLoanWizard); QString txt; //FIXME: port if (! q->field("finalPaymentEditValid").toBool()) { txt = QString("<") + i18n("calculate") + QString(">"); } else { txt = q->field("finalPaymentEdit").value().formatMoney(m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId()))); } q->setField("balloon1", txt); q->setField("balloon2", txt); q->setField("balloon3", txt); q->setField("balloon4", txt); q->setField("balloon5", txt); } void updateLoanInfo() { Q_Q(KNewLoanWizard); updateLoanAmount(); updateInterestRate(); updateDuration(); updatePayment(); updateFinalPayment(); ui->m_additionalFeesPage->updatePeriodicPayment(m_account); QString txt; int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())); q->setField("loanAmount6", q->field("loanAmountEdit").value().formatMoney(fraction)); q->setField("interestRate6", QString(q->field("interestRateEdit").value().formatMoney("", 3) + QString("%"))); txt = QString().sprintf("%d ", q->field("durationValueEdit").toInt()) + q->field("durationUnitEdit").toString(); q->setField("duration6", txt); q->setField("payment6", q->field("paymentEdit").value().formatMoney(fraction)); q->setField("balloon6", q->field("finalPaymentEdit").value().formatMoney(fraction)); } int calculateLoan() { Q_Q(KNewLoanWizard); MyMoneyFinancialCalculator calc; double val; int PF; QString result; // FIXME: for now, we only support interest calculation at the end of the period calc.setBep(); // FIXME: for now, we only support periodic compounding calc.setDisc(); PF = MyMoneySchedule::eventsPerYear(eMyMoney::Schedule::Occurrence(q->field("paymentFrequencyUnitEdit").toInt())); if (PF == 0) return 0; calc.setPF(PF); // FIXME: for now we only support compounding frequency == payment frequency calc.setCF(PF); if (q->field("loanAmountEditValid").toBool()) { val = q->field("loanAmountEdit").value().abs().toDouble(); if (q->field("borrowButton").toBool()) val = -val; calc.setPv(val); } if (q->field("interestRateEditValid").toBool()) { val = q->field("interestRateEdit").value().abs().toDouble(); calc.setIr(val); } if (q->field("paymentEditValid").toBool()) { val = q->field("paymentEdit").value().abs().toDouble(); if (q->field("lendButton").toBool()) val = -val; calc.setPmt(val); } if (q->field("finalPaymentEditValid").toBool()) { val = q->field("finalPaymentEditValid").value().abs().toDouble(); if (q->field("lendButton").toBool()) val = -val; calc.setFv(val); } if (q->field("durationValueEdit").toInt() != 0) { calc.setNpp(ui->m_durationPage->term()); } int fraction = m_account.fraction(MyMoneyFile::instance()->security(m_account.currencyId())); // setup of parameters is done, now do the calculation try { //FIXME: port if (!q->field("loanAmountEditValid").toBool()) { // calculate the amount of the loan out of the other information val = calc.presentValue(); ui->m_loanAmountPage->ui->m_loanAmountEdit->loadText(MyMoneyMoney(static_cast(val)).abs().formatMoney(fraction)); result = i18n("KMyMoney has calculated the amount of the loan as %1.", ui->m_loanAmountPage->ui->m_loanAmountEdit->lineedit()->text()); } else if (!q->field("interestRateEditValid").toBool()) { // calculate the interest rate out of the other information val = calc.interestRate(); ui->m_interestPage->ui->m_interestRateEdit->loadText(MyMoneyMoney(static_cast(val)).abs().formatMoney("", 3)); result = i18n("KMyMoney has calculated the interest rate to %1%.", ui->m_interestPage->ui->m_interestRateEdit->lineedit()->text()); } else if (!q->field("paymentEditValid").toBool()) { // calculate the periodical amount of the payment out of the other information val = calc.payment(); q->setField("paymentEdit", QVariant::fromValue(MyMoneyMoney(val).abs())); // reset payment as it might have changed due to rounding val = q->field("paymentEdit").value().abs().toDouble(); if (q->field("lendButton").toBool()) val = -val; calc.setPmt(val); result = i18n("KMyMoney has calculated a periodic payment of %1 to cover principal and interest.", ui->m_paymentPage->ui->m_paymentEdit->lineedit()->text()); val = calc.futureValue(); if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) >= qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) >= qAbs(calc.payment()))) { calc.setNpp(calc.npp() - 1); ui->m_durationPage->updateTermWidgets(calc.npp()); val = calc.futureValue(); MyMoneyMoney refVal(static_cast(val)); ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += QString(" "); result += i18n("The number of payments has been decremented and the final payment has been modified to %1.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->lineedit()->text()); } else if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) < qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) < qAbs(calc.payment()))) { ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(MyMoneyMoney().formatMoney(fraction)); } else { MyMoneyMoney refVal(static_cast(val)); ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += i18n("The final payment has been modified to %1.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->lineedit()->text()); } } else if (q->field("durationValueEdit").toInt() == 0) { // calculate the number of payments out of the other information val = calc.numPayments(); if (val == 0) throw MYMONEYEXCEPTION("incorrect fincancial calculation"); // if the number of payments has a fractional part, then we // round it to the smallest integer and calculate the balloon payment result = i18n("KMyMoney has calculated the term of your loan as %1. ", ui->m_durationPage->updateTermWidgets(qFloor(val))); if (val != qFloor(val)) { calc.setNpp(qFloor(val)); val = calc.futureValue(); MyMoneyMoney refVal(static_cast(val)); ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); result += i18n("The final payment has been modified to %1.", ui->m_finalPaymentPage->ui->m_finalPaymentEdit->lineedit()->text()); } } else { // calculate the future value of the loan out of the other information val = calc.futureValue(); // we differentiate between the following cases: // a) the future value is greater than a payment // b) the future value is less than a payment or the loan is overpaid // c) all other cases // // a) means, we have paid more than we owed. This can't be // b) means, we paid more than we owed but the last payment is // less in value than regular payments. That means, that the // future value is to be treated as (fully payed back) // c) the loan is not payed back yet if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) > qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) > qAbs(calc.payment()))) { // case a) qDebug("Future Value is %f", val); throw MYMONEYEXCEPTION("incorrect fincancial calculation"); } else if ((q->field("borrowButton").toBool() && val < 0 && qAbs(val) <= qAbs(calc.payment())) || (q->field("lendButton").toBool() && val > 0 && qAbs(val) <= qAbs(calc.payment()))) { // case b) val = 0; } MyMoneyMoney refVal(static_cast(val)); result = i18n("KMyMoney has calculated a final payment of %1 for this loan.", refVal.abs().formatMoney(fraction)); if (q->field("finalPaymentEditValid").toBool()) { if ((q->field("finalPaymentEdit").value().abs() - refVal.abs()).abs().toDouble() > 1) { throw MYMONEYEXCEPTION("incorrect fincancial calculation"); } result = i18n("KMyMoney has successfully verified your loan information."); } //FIXME: port ui->m_finalPaymentPage->ui->m_finalPaymentEdit->loadText(refVal.abs().formatMoney(fraction)); } } catch (const MyMoneyException &) { KMessageBox::error(0, i18n("You have entered mis-matching information. Please backup to the " "appropriate page and update your figures or leave one value empty " "to let KMyMoney calculate it for you"), i18n("Calculation error")); return 0; } result += i18n("\n\nAccept this or modify the loan information and recalculate."); KMessageBox::information(0, result, i18n("Calculation successful")); return 1; } /** * This method returns the transaction that is stored within * the schedule. See schedule(). * * @return MyMoneyTransaction object to be used within the schedule */ MyMoneyTransaction transaction() const { Q_Q(const KNewLoanWizard); MyMoneyTransaction t; bool hasInterest = !q->field("interestRateEdit").value().isZero(); MyMoneySplit sPayment, sInterest, sAmortization; // setup accounts. at this point, we cannot fill in the id of the // account that the amortization will be performed on, because we // create the account. So the id is yet unknown. sPayment.setAccountId(q->field("paymentAccountEdit").toStringList().first()); //Only create the interest split if not zero if (hasInterest) { sInterest.setAccountId(q->field("interestAccountEdit").toStringList().first()); sInterest.setValue(MyMoneyMoney::autoCalc); sInterest.setShares(sInterest.value()); sInterest.setAction(MyMoneySplit::ActionInterest); } // values if (q->field("borrowButton").toBool()) { sPayment.setValue(-q->field("paymentEdit").value()); } else { sPayment.setValue(q->field("paymentEdit").value()); } sAmortization.setValue(MyMoneyMoney::autoCalc); // don't forget the shares sPayment.setShares(sPayment.value()); sAmortization.setShares(sAmortization.value()); // setup the commodity MyMoneyAccount acc = MyMoneyFile::instance()->account(sPayment.accountId()); t.setCommodity(acc.currencyId()); // actions sPayment.setAction(MyMoneySplit::ActionAmortization); sAmortization.setAction(MyMoneySplit::ActionAmortization); // payee QString payeeId = q->field("payeeEdit").toString(); sPayment.setPayeeId(payeeId); sAmortization.setPayeeId(payeeId); MyMoneyAccount account("Phony-ID", MyMoneyAccount()); sAmortization.setAccountId(account.id()); // IMPORTANT: Payment split must be the first one, because // the schedule view expects it this way during display t.addSplit(sPayment); t.addSplit(sAmortization); if (hasInterest) { t.addSplit(sInterest); } // copy the splits from the other costs and update the payment split foreach (const MyMoneySplit& it, m_transaction.splits()) { if (it.accountId() != account.id()) { MyMoneySplit sp = it; sp.clearId(); t.addSplit(sp); sPayment.setValue(sPayment.value() - sp.value()); sPayment.setShares(sPayment.value()); t.modifySplit(sPayment); } } return t; } void loadAccountList() { Q_Q(KNewLoanWizard); AccountSet interestSet, assetSet; if (q->field("borrowButton").toBool()) { interestSet.addAccountType(eMyMoney::Account::Type::Expense); } else { interestSet.addAccountType(eMyMoney::Account::Type::Income); } if (ui->m_interestCategoryPage) interestSet.load(ui->m_interestCategoryPage->ui->m_interestAccountEdit); assetSet.addAccountType(eMyMoney::Account::Type::Checkings); assetSet.addAccountType(eMyMoney::Account::Type::Savings); assetSet.addAccountType(eMyMoney::Account::Type::Cash); assetSet.addAccountType(eMyMoney::Account::Type::Asset); assetSet.addAccountType(eMyMoney::Account::Type::Currency); if (ui->m_assetAccountPage) assetSet.load(ui->m_assetAccountPage->ui->m_assetAccountEdit); assetSet.addAccountType(eMyMoney::Account::Type::CreditCard); assetSet.addAccountType(eMyMoney::Account::Type::Liability); if (ui->m_schedulePage) assetSet.load(ui->m_schedulePage->ui->m_paymentAccountEdit); } KNewLoanWizard *q_ptr; Ui::KNewLoanWizard *ui; MyMoneyAccountLoan m_account; MyMoneyTransaction m_transaction; MyMoneySplit m_split; QBitArray m_pages; }; #endif