diff --git a/kmymoney/CMakeLists.txt b/kmymoney/CMakeLists.txt index 281865264..2655120a6 100644 --- a/kmymoney/CMakeLists.txt +++ b/kmymoney/CMakeLists.txt @@ -1,157 +1,157 @@ include_directories( ${LIBALKIMIA_INCLUDE_DIR} ) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${KDEPIMLIBS_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}/dialogs/ ${CMAKE_CURRENT_SOURCE_DIR}/widgets/ ${CMAKE_CURRENT_BINARY_DIR}/widgets/ ${CMAKE_CURRENT_BINARY_DIR}/settings/ ${CMAKE_CURRENT_SOURCE_DIR}/mymoney/ ${CMAKE_CURRENT_SOURCE_DIR}/mymoney/storage/ ${CMAKE_CURRENT_SOURCE_DIR}/plugins/ ${CMAKE_CURRENT_BINARY_DIR}/plugins/ ${CMAKE_CURRENT_SOURCE_DIR}/views/ ${CMAKE_CURRENT_SOURCE_DIR}/dialogs/ ${CMAKE_CURRENT_SOURCE_DIR}/converter/ ${CMAKE_CURRENT_BINARY_DIR}/dialogs/settings/ ${CMAKE_CURRENT_BINARY_DIR}/mymoney/storage/ ${CMAKE_CURRENT_BINARY_DIR}/mymoney/ ${CMAKE_CURRENT_SOURCE_DIR}/reports/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/endingbalancedlg/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/endingbalancedlg/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/newinvestmentwizard/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/newinvestmentwizard/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/newloanwizard/ ${CMAKE_CURRENT_BINARY_DIR}/wizards/newloanwizard/ ${CMAKE_CURRENT_SOURCE_DIR}/wizards/wizardpages/ ${CMAKE_CURRENT_SOURCE_DIR}/models/ ${CMAKE_CURRENT_BINARY_DIR}/models/ ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/ibanandbic/widgets/ # TODO: this line should be moved to the target it belongs ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/ibanandbic/ ${CMAKE_CURRENT_BINARY_DIR}/payeeidentifier/nationalaccount/ ${KMyMoney_SOURCE_DIR}/libkgpgfile/ ${KMyMoney_SOURCE_DIR}/libkdchart/include/KDChart ) add_subdirectory( settings ) add_subdirectory( mymoney ) add_subdirectory( models ) add_subdirectory( plugins ) add_subdirectory( reports ) add_subdirectory( widgets ) add_subdirectory( dialogs ) add_subdirectory( views ) add_subdirectory( converter ) add_subdirectory( wizards ) add_subdirectory( pics ) add_subdirectory( html ) add_subdirectory( templates ) add_subdirectory( misc ) add_subdirectory( payeeidentifier ) set( _HEADERS kmymoneyutils.h kmymoneyglobalsettings.h ) ########### common code (kmymoney_common) STATIC ############### # will be linked into kmymoney, kmymoneytest, and libkmymoney.so set( kmymoney_common_SRCS kmymoneyutils.cpp kstartuplogo.cpp kmymoneyglobalsettings.cpp ) kde4_add_library(kmymoney_common STATIC ${kmymoney_common_SRCS}) target_link_libraries(kmymoney_common ${QT_AND_KDECORE_LIBS} ${KDEPIMLIBS_KHOLIDAYS_LIBS} ${LIBALKIMIA_LIBRARIES} kmm_widgets kmm_mymoney) # must build kmymoney/transactionsortoption.h # from transactionsortoption.ui first add_dependencies(kmymoney_common generate_base_ui_srcs kmm_settings) add_dependencies(wizardpages widgets) set( kmymoney_common_LIBS views reports kmymoney_base kmymoney_common newuserwizard newaccountwizard newinvestmentwizard newloanwizard endingbalancedlg wizardpages dialogs widgets settings converter models kmm_settings kmm_widgets - kmm_storage kmm_mymoney kgpgfile interfaces kmm_plugin dialogs kmm_kdchart + kmm_storage kmm_mymoney kgpgfile interfaces kmm_plugin dialogs kmm_kdchart kmm_utils_validators ${QT_AND_KDECORE_LIBS} ${QT_QTSQL_LIBRARY} ${KDE4_KHTML_LIBS} ${KDE4_KDEUI_LIBS} ${KDEPIMLIBS_KPIMIDENTITIES_LIBS} ${KDEPIMLIBS_AKONADI_CONTACT_LIBS} ${KDE4_KFILE_LIBS} ${KDE4_KUTILS_LIBRARY} ) ########### kmymoney executable ############### set( kmymoney_SRCS main.cpp kmymoney.cpp ) qt4_add_dbus_adaptor(kmymoney_SRCS org.kde.kmymoney.xml kmymoney.h KMyMoneyApp) kde4_add_app_icon( kmymoney_SRCS hi*-app-kmymoney.png ) kde4_add_executable( kmymoney ${kmymoney_SRCS} ) -target_link_libraries( kmymoney ${kmymoney_common_LIBS} ) +target_link_libraries( kmymoney ${kmymoney_common_LIBS} kmm_mymoney) ########### install files ############### install(TARGETS kmymoney ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES kmymoney.upd DESTINATION ${KCONF_UPDATE_INSTALL_DIR} ) install(FILES kmymoneyui.rc tips DESTINATION ${DATA_INSTALL_DIR}/kmymoney ) install(FILES ${_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney ) install(FILES org.kde.kmymoney.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install(FILES org.kde.kmymoney.appdata.xml DESTINATION ${SHARE_INSTALL_PREFIX}/metainfo/ ) install(FILES x-kmymoney.xml DESTINATION ${XDG_MIME_INSTALL_DIR}) update_xdg_mimetypes(${XDG_MIME_INSTALL_DIR}) ########## Application Icons ############# install(DIRECTORY icons/hicolor DESTINATION ${DATA_INSTALL_DIR}/kmymoney/icons PATTERN "*Make*" EXCLUDE PATTERN ".svn" EXCLUDE ) install(DIRECTORY icons/oxygen DESTINATION ${DATA_INSTALL_DIR}/kmymoney/icons PATTERN "*Make*" EXCLUDE PATTERN ".svn" EXCLUDE ) kde4_install_icons(${ICON_INSTALL_DIR}) ############## tests #################### if( KDE4_BUILD_TESTS ) set(target_link_libraries_test kmm_mymoney kmymoney_common kmm_settings widgets ${KDE4_KHTML_LIBS} ${KDE4_KDEUI_LIBS} ${KDE4_KFILE_LIBS} ${KDE4_KUTILS_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTSQL_LIBRARY} ${QT_QTTEST_LIBRARY} ${LIBALKIMIA_LIBRARY} ) # KMyMoneyUtils set( kmymoneyutilstest_SRCS kmymoneyutilstest.cpp ) kde4_add_unit_test( kmymoneyutilstest TESTNAME kmymoney-utilstest ${kmymoneyutilstest_SRCS} ) add_dependencies( kmymoneyutilstest kmymoney_common kmm_mymoney ) target_link_libraries( kmymoneyutilstest ${target_link_libraries_test}) endif( KDE4_BUILD_TESTS ) diff --git a/kmymoney/misc/CMakeLists.txt b/kmymoney/misc/CMakeLists.txt index 3989496ce..57a58c0b2 100644 --- a/kmymoney/misc/CMakeLists.txt +++ b/kmymoney/misc/CMakeLists.txt @@ -1,25 +1,27 @@ install(PROGRAMS financequote.pl DESTINATION ${DATA_INSTALL_DIR}/kmymoney/misc) set( kmm_utils_validators_SRCS validators.cpp charvalidator.cpp + debugindenter.cpp ) set( kmm_utils_validators_HEADER validators.h charvalidator.h + debugindenter.h ) kde4_add_library(kmm_utils_validators STATIC ${kmm_utils_validators_SRCS}) target_link_libraries( kmm_utils_validators ${QT_QTCORE_LIBRARIES} ) #INSTALL(TARGETS kmm_utils_validators # ${INSTALL_TARGETS_DEFAULT_ARGS} ) INSTALL(FILES ${kmm_utils_validators_HEADER} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney/misc COMPONENT Devel) diff --git a/kmymoney/misc/debugindenter.cpp b/kmymoney/misc/debugindenter.cpp new file mode 100644 index 000000000..0583a91a1 --- /dev/null +++ b/kmymoney/misc/debugindenter.cpp @@ -0,0 +1,25 @@ +/* + Copyright 2017 ralf.habacker + + 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 "debugindenter.h" + +int DebugIndenter::m_level = 0; + + diff --git a/kmymoney/misc/debugindenter.h b/kmymoney/misc/debugindenter.h new file mode 100644 index 000000000..9e2f5a58e --- /dev/null +++ b/kmymoney/misc/debugindenter.h @@ -0,0 +1,77 @@ +/* + Copyright 2017 ralf.habacker + + 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 DEBUGINDENTER_H +#define DEBUGINDENTER_H + +#include + +#include +#include + +#include "mymoneyobject.h" + +class DebugIndenter : public QDebug +{ +public: + DebugIndenter(QDebug &dbg, const char *className) + : QDebug(dbg), m_dbg(dbg) + { + init(className); + } + + DebugIndenter(QDebug &dbg, MyMoneyObject *o) + : QDebug(dbg), m_dbg(dbg) + { + init(typeid(*o).name()); + } + + DebugIndenter(QDebug &dbg, const MyMoneyObject &o) + : QDebug(dbg), m_dbg(dbg) + { + init(typeid(o).name()); + } + + void init(const char *name) + { + int status; + m_level++; + m_dbg.nospace() << "\n" << fill().toLatin1().constData() << abi::__cxa_demangle(name, 0, 0, &status) << "("; + m_dbg.space(); + } + + QString fill() + { + return QString().fill(QLatin1Char(' '), m_level*2); + } + + ~DebugIndenter() + { + --m_level; + m_dbg.nospace() << ")\n" << fill().toLatin1().constData(); + m_dbg.space(); + } + +protected: + static int m_level; + QDebug &m_dbg; +}; + +#endif // DEBUGINDENTER_H diff --git a/kmymoney/mymoney/mymoneyaccount.cpp b/kmymoney/mymoney/mymoneyaccount.cpp index 2d35f0c29..9a0e61c2b 100644 --- a/kmymoney/mymoney/mymoneyaccount.cpp +++ b/kmymoney/mymoney/mymoneyaccount.cpp @@ -1,864 +1,886 @@ /*************************************************************************** mymoneyaccount.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2002 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. * * * ***************************************************************************/ #include "mymoneyaccount.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include -#include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" #include "mymoneysplit.h" #include "mymoneyfile.h" #include "payeeidentifier/payeeidentifiertyped.h" #include "payeeidentifier/ibanandbic/ibanbic.h" #include "payeeidentifier/nationalaccount/nationalaccount.h" +#include "misc/debugindenter.h" MyMoneyAccount::MyMoneyAccount() : m_accountType(UnknownAccountType), m_fraction(-1) { } MyMoneyAccount::~MyMoneyAccount() { } MyMoneyAccount::MyMoneyAccount(const QString& id, const MyMoneyAccount& right) : MyMoneyObject(id) { *this = right; setId(id); } MyMoneyAccount::MyMoneyAccount(const QDomElement& node) : MyMoneyObject(node), MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement()), m_accountType(UnknownAccountType), m_fraction(-1) { if ("ACCOUNT" != node.tagName()) throw MYMONEYEXCEPTION("Node was not ACCOUNT"); setName(node.attribute("name")); // qDebug("Reading information for account %s", acc.name().data()); setParentAccountId(QStringEmpty(node.attribute("parentaccount"))); setLastModified(stringToDate(QStringEmpty(node.attribute("lastmodified")))); setLastReconciliationDate(stringToDate(QStringEmpty(node.attribute("lastreconciled")))); if (!m_lastReconciliationDate.isValid()) { // for some reason, I was unable to access our own kvp at this point through // the value() method. It always returned empty strings. The workaround for // this is to construct a local kvp the same way as we have done before and // extract the value from it. // // Since we want to get rid of the lastStatementDate record anyway, this seems // to be ok for now. (ipwizard - 2008-08-14) QString txt = MyMoneyKeyValueContainer(node.elementsByTagName("KEYVALUEPAIRS").item(0).toElement()).value("lastStatementDate"); if (!txt.isEmpty()) { setLastReconciliationDate(QDate::fromString(txt, Qt::ISODate)); } } setInstitutionId(QStringEmpty(node.attribute("institution"))); setNumber(QStringEmpty(node.attribute("number"))); setOpeningDate(stringToDate(QStringEmpty(node.attribute("opened")))); setCurrencyId(QStringEmpty(node.attribute("currency"))); QString tmp = QStringEmpty(node.attribute("type")); bool bOK = false; int type = tmp.toInt(&bOK); if (bOK) { setAccountType(static_cast(type)); } else { qWarning("XMLREADER: Account %s had invalid or no account type information.", qPrintable(name())); } if (node.hasAttribute("openingbalance")) { if (!MyMoneyMoney(node.attribute("openingbalance")).isZero()) { QString msg = i18n("Account %1 contains an opening balance. Please use KMyMoney version 0.8 or later and earlier than version 0.9 to correct the problem.", m_name); throw MYMONEYEXCEPTION(msg); } } setDescription(node.attribute("description")); m_id = QStringEmpty(node.attribute("id")); // qDebug("Account %s has id of %s, type of %d, parent is %s.", acc.name().data(), id.data(), type, acc.parentAccountId().data()); // Process any Sub-Account information found inside the account entry. m_accountList.clear(); QDomNodeList nodeList = node.elementsByTagName("SUBACCOUNTS"); if (nodeList.count() > 0) { nodeList = nodeList.item(0).toElement().elementsByTagName("SUBACCOUNT"); for (int i = 0; i < nodeList.count(); ++i) { addAccountId(QString(nodeList.item(i).toElement().attribute("id"))); } } nodeList = node.elementsByTagName("ONLINEBANKING"); if (nodeList.count() > 0) { QDomNamedNodeMap attributes = nodeList.item(0).toElement().attributes(); for (int i = 0; i < attributes.count(); ++i) { const QDomAttr& it_attr = attributes.item(i).toAttr(); m_onlineBankingSettings.setValue(it_attr.name(), it_attr.value()); } } // Up to and including version 4.6.6 the new account dialog stored the iban in the kvp-key "IBAN". // But the rest of the software uses "iban". So correct this: if (!value("IBAN").isEmpty()) { // If "iban" was not set, set it now. If it is set, the user reseted it already, so remove // the garbage. if (value("iban").isEmpty()) setValue("iban", value("IBAN")); deletePair("IBAN"); } } void MyMoneyAccount::setName(const QString& name) { m_name = name; } void MyMoneyAccount::setNumber(const QString& number) { m_number = number; } void MyMoneyAccount::setDescription(const QString& desc) { m_description = desc; } void MyMoneyAccount::setInstitutionId(const QString& id) { m_institution = id; } void MyMoneyAccount::setLastModified(const QDate& date) { m_lastModified = date; } void MyMoneyAccount::setOpeningDate(const QDate& date) { m_openingDate = date; } void MyMoneyAccount::setLastReconciliationDate(const QDate& date) { // FIXME: for a limited time (maybe until we delivered 1.0) we // keep the last reconciliation date also in the KVP for backward // compatibility. After that, the setValue() statemetn should be removed // and the XML ctor should remove the value completely from the KVP setValue("lastStatementDate", date.toString(Qt::ISODate)); m_lastReconciliationDate = date; } void MyMoneyAccount::setParentAccountId(const QString& parent) { m_parentAccount = parent; } void MyMoneyAccount::setAccountType(const accountTypeE type) { m_accountType = type; } void MyMoneyAccount::addAccountId(const QString& account) { if (!m_accountList.contains(account)) m_accountList += account; } void MyMoneyAccount::removeAccountIds() { m_accountList.clear(); } void MyMoneyAccount::removeAccountId(const QString& account) { int pos; pos = m_accountList.indexOf(account); if (pos != -1) m_accountList.removeAt(pos); } bool MyMoneyAccount::operator == (const MyMoneyAccount& right) const { return (MyMoneyKeyValueContainer::operator==(right) && MyMoneyObject::operator==(right) && (m_accountList == right.m_accountList) && (m_accountType == right.m_accountType) && (m_lastModified == right.m_lastModified) && (m_lastReconciliationDate == right.m_lastReconciliationDate) && ((m_name.length() == 0 && right.m_name.length() == 0) || (m_name == right.m_name)) && ((m_number.length() == 0 && right.m_number.length() == 0) || (m_number == right.m_number)) && ((m_description.length() == 0 && right.m_description.length() == 0) || (m_description == right.m_description)) && (m_openingDate == right.m_openingDate) && (m_parentAccount == right.m_parentAccount) && (m_currencyId == right.m_currencyId) && (m_institution == right.m_institution)); } MyMoneyAccount::accountTypeE MyMoneyAccount::accountGroup() const { switch (m_accountType) { case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::Currency: case MyMoneyAccount::Investment: case MyMoneyAccount::MoneyMarket: case MyMoneyAccount::CertificateDep: case MyMoneyAccount::AssetLoan: case MyMoneyAccount::Stock: return MyMoneyAccount::Asset; case MyMoneyAccount::CreditCard: case MyMoneyAccount::Loan: return MyMoneyAccount::Liability; default: return m_accountType; } } void MyMoneyAccount::setCurrencyId(const QString& id) { m_currencyId = id; } bool MyMoneyAccount::isAssetLiability() const { return accountGroup() == Asset || accountGroup() == Liability; } bool MyMoneyAccount::isIncomeExpense() const { return accountGroup() == Income || accountGroup() == Expense; } bool MyMoneyAccount::isLoan() const { return accountType() == Loan || accountType() == AssetLoan; } bool MyMoneyAccount::isInvest() const { return accountType() == Stock; } bool MyMoneyAccount::isLiquidAsset() const { return accountType() == Checkings || accountType() == Savings || accountType() == Cash; } template<> QList< payeeIdentifierTyped< ::payeeIdentifiers::ibanBic> > MyMoneyAccount::payeeIdentifiersByType() const { payeeIdentifierTyped ident = payeeIdentifierTyped(new payeeIdentifiers::ibanBic); ident->setIban(value(QLatin1String("iban"))); if (!institutionId().isEmpty()) { const MyMoneyInstitution institution = MyMoneyFile::instance()->institution(institutionId()); ident->setBic(institution.value("bic")); } ident->setOwnerName(MyMoneyFile::instance()->user().name()); QList< payeeIdentifierTyped > typedList; typedList << ident; return typedList; } MyMoneyAccountLoan::MyMoneyAccountLoan(const MyMoneyAccount& acc) : MyMoneyAccount(acc) { } const MyMoneyMoney MyMoneyAccountLoan::loanAmount() const { return MyMoneyMoney(value("loan-amount")); } void MyMoneyAccountLoan::setLoanAmount(const MyMoneyMoney& amount) { setValue("loan-amount", amount.toString()); } const MyMoneyMoney MyMoneyAccountLoan::interestRate(const QDate& date) const { MyMoneyMoney rate; QString key; QString val; if (!date.isValid()) return rate; key.sprintf("ir-%04d-%02d-%02d", date.year(), date.month(), date.day()); QRegExp regExp("ir-(\\d{4})-(\\d{2})-(\\d{2})"); QMap::ConstIterator it; for (it = pairs().begin(); it != pairs().end(); ++it) { if (regExp.indexIn(it.key()) > -1) { if (qstrcmp(it.key().toLatin1(), key.toLatin1()) <= 0) val = *it; else break; } else if (!val.isEmpty()) break; } if (!val.isEmpty()) { rate = MyMoneyMoney(val); } return rate; } void MyMoneyAccountLoan::setInterestRate(const QDate& date, const MyMoneyMoney& value) { if (!date.isValid()) return; QString key; key.sprintf("ir-%04d-%02d-%02d", date.year(), date.month(), date.day()); setValue(key, value.toString()); } MyMoneyAccountLoan::interestDueE MyMoneyAccountLoan::interestCalculation() const { QString payTime(value("interest-calculation")); if (payTime == "paymentDue") return paymentDue; return paymentReceived; } void MyMoneyAccountLoan::setInterestCalculation(const MyMoneyAccountLoan::interestDueE onReception) { if (onReception == paymentDue) setValue("interest-calculation", "paymentDue"); else setValue("interest-calculation", "paymentReceived"); } const QDate MyMoneyAccountLoan::nextInterestChange() const { QDate rc; QRegExp regExp("(\\d{4})-(\\d{2})-(\\d{2})"); if (regExp.indexIn(value("interest-nextchange")) != -1) { rc.setYMD(regExp.cap(1).toInt(), regExp.cap(2).toInt(), regExp.cap(3).toInt()); } return rc; } void MyMoneyAccountLoan::setNextInterestChange(const QDate& date) { setValue("interest-nextchange", date.toString(Qt::ISODate)); } int MyMoneyAccountLoan::interestChangeFrequency(int* unit) const { int rc = -1; if (unit) *unit = 1; QRegExp regExp("(\\d+)/(\\d{1})"); if (regExp.indexIn(value("interest-changefrequency")) != -1) { rc = regExp.cap(1).toInt(); if (unit != 0) { *unit = regExp.cap(2).toInt(); } } return rc; } void MyMoneyAccountLoan::setInterestChangeFrequency(const int amount, const int unit) { QString val; val.sprintf("%d/%d", amount, unit); setValue("interest-changeFrequency", val); } const QString MyMoneyAccountLoan::schedule() const { return QString(value("schedule").toLatin1()); } void MyMoneyAccountLoan::setSchedule(const QString& sched) { setValue("schedule", sched); } bool MyMoneyAccountLoan::fixedInterestRate() const { // make sure, that an empty kvp element returns true return !(value("fixed-interest") == "no"); } void MyMoneyAccountLoan::setFixedInterestRate(const bool fixed) { setValue("fixed-interest", fixed ? "yes" : "no"); if (fixed) { deletePair("interest-nextchange"); deletePair("interest-changeFrequency"); } } const MyMoneyMoney MyMoneyAccountLoan::finalPayment() const { return MyMoneyMoney(value("final-payment")); } void MyMoneyAccountLoan::setFinalPayment(const MyMoneyMoney& finalPayment) { setValue("final-payment", finalPayment.toString()); } unsigned int MyMoneyAccountLoan::term() const { return value("term").toUInt(); } void MyMoneyAccountLoan::setTerm(const unsigned int payments) { setValue("term", QString::number(payments)); } const MyMoneyMoney MyMoneyAccountLoan::periodicPayment() const { return MyMoneyMoney(value("periodic-payment")); } void MyMoneyAccountLoan::setPeriodicPayment(const MyMoneyMoney& payment) { setValue("periodic-payment", payment.toString()); } const QString MyMoneyAccountLoan::payee() const { return value("payee"); } void MyMoneyAccountLoan::setPayee(const QString& payee) { setValue("payee", payee); } const QString MyMoneyAccountLoan::interestAccountId() const { return QString(); } void MyMoneyAccountLoan::setInterestAccountId(const QString& /* id */) { } bool MyMoneyAccountLoan::hasReferenceTo(const QString& id) const { return MyMoneyAccount::hasReferenceTo(id) || (id == payee()) || (id == schedule()); } void MyMoneyAccountLoan::setInterestCompounding(int frequency) { setValue("compoundingFrequency", QString("%1").arg(frequency)); } int MyMoneyAccountLoan::interestCompounding() const { return value("compoundingFrequency").toInt(); } void MyMoneyAccount::writeXML(QDomDocument& document, QDomElement& parent) const { QDomElement el = document.createElement("ACCOUNT"); writeBaseXML(document, el); el.setAttribute("parentaccount", parentAccountId()); el.setAttribute("lastreconciled", dateToString(lastReconciliationDate())); el.setAttribute("lastmodified", dateToString(lastModified())); el.setAttribute("institution", institutionId()); el.setAttribute("opened", dateToString(openingDate())); el.setAttribute("number", number()); // el.setAttribute("openingbalance", openingBalance().toString()); el.setAttribute("type", accountType()); el.setAttribute("name", name()); el.setAttribute("description", description()); if (!currencyId().isEmpty()) el.setAttribute("currency", currencyId()); //Add in subaccount information, if this account has subaccounts. if (accountCount()) { QDomElement subAccounts = document.createElement("SUBACCOUNTS"); QStringList::ConstIterator it; for (it = accountList().begin(); it != accountList().end(); ++it) { QDomElement temp = document.createElement("SUBACCOUNT"); temp.setAttribute("id", (*it)); subAccounts.appendChild(temp); } el.appendChild(subAccounts); } // Write online banking settings if (m_onlineBankingSettings.pairs().count()) { QDomElement onlinesettings = document.createElement("ONLINEBANKING"); QMap::const_iterator it_key = m_onlineBankingSettings.pairs().begin(); while (it_key != m_onlineBankingSettings.pairs().end()) { onlinesettings.setAttribute(it_key.key(), it_key.value()); ++it_key; } el.appendChild(onlinesettings); } // FIXME drop the lastStatementDate record from the KVP when it is // not stored there after setLastReconciliationDate() has been changed // See comment there when this will happen // deletePair("lastStatementDate"); //Add in Key-Value Pairs for accounts. MyMoneyKeyValueContainer::writeXML(document, el); parent.appendChild(el); } bool MyMoneyAccount::hasReferenceTo(const QString& id) const { return (id == m_institution) || (id == m_parentAccount) || (id == m_currencyId); } void MyMoneyAccount::setOnlineBankingSettings(const MyMoneyKeyValueContainer& values) { m_onlineBankingSettings = values; } const MyMoneyKeyValueContainer& MyMoneyAccount::onlineBankingSettings() const { return m_onlineBankingSettings; } void MyMoneyAccount::setClosed(bool closed) { if (closed) setValue("mm-closed", "yes"); else deletePair("mm-closed"); } bool MyMoneyAccount::isClosed() const { return !(value("mm-closed").isEmpty()); } int MyMoneyAccount::fraction(const MyMoneySecurity& sec) const { int fraction; if (m_accountType == Cash) fraction = sec.smallestCashFraction(); else fraction = sec.smallestAccountFraction(); return fraction; } int MyMoneyAccount::fraction(const MyMoneySecurity& sec) { if (m_accountType == Cash) m_fraction = sec.smallestCashFraction(); else m_fraction = sec.smallestAccountFraction(); return m_fraction; } int MyMoneyAccount::fraction() const { return m_fraction; } bool MyMoneyAccount::isCategory() const { return m_accountType == Income || m_accountType == Expense; } QString MyMoneyAccount::brokerageName() const { if (m_accountType == Investment) return QString("%1 (%2)").arg(m_name, i18nc("Brokerage (suffix for account names)", "Brokerage")); return m_name; } void MyMoneyAccount::adjustBalance(const MyMoneySplit& s, bool reverse) { if (s.action() == MyMoneySplit::ActionSplitShares) { if (reverse) m_balance = m_balance / s.shares(); else m_balance = m_balance * s.shares(); } else { if (reverse) m_balance -= s.shares(); else m_balance += s.shares(); } } QPixmap MyMoneyAccount::accountPixmap(bool reconcileFlag, int size) const { QString icon; switch (accountType()) { default: if (accountGroup() == MyMoneyAccount::Asset) icon = "view-bank-account"; else icon = "view-loan"; break; case MyMoneyAccount::Investment: case MyMoneyAccount::Stock: case MyMoneyAccount::MoneyMarket: case MyMoneyAccount::CertificateDep: icon = "view-stock-account"; break; case MyMoneyAccount::Checkings: icon = "view-bank-account-checking"; break; case MyMoneyAccount::Savings: icon = "view-bank-account-savings"; break; case MyMoneyAccount::AssetLoan: icon = "view-loan-asset"; break; case MyMoneyAccount::Loan: icon = "view-loan"; break; case MyMoneyAccount::CreditCard: icon = "view-credit-card-account"; break; case MyMoneyAccount::Asset: icon = "view-bank-account"; break; case MyMoneyAccount::Cash: icon = "account-types-cash"; break; case MyMoneyAccount::Income: icon = "view-income-categories"; break; case MyMoneyAccount::Expense: icon = "view-expenses-categories"; break; case MyMoneyAccount::Equity: icon = "view-bank-account"; break; } QString iconKey = icon + QString(size); QPixmap result; if (!QPixmapCache::find(iconKey, result)) { result = DesktopIcon(icon, size); QPixmapCache::insert(iconKey, result); } QPainter pixmapPainter(&result); if (isClosed()) { QPixmap ovly = DesktopIcon("dialog-close", size); pixmapPainter.drawPixmap(ovly.width() / 2, ovly.height() / 2, ovly.width() / 2, ovly.height() / 2, ovly); } else if (reconcileFlag) { QPixmap ovly = DesktopIcon("flag-green", size); pixmapPainter.drawPixmap(size / 2, size / 2, ovly.width() / 2, ovly.height() / 2, ovly); } else if (!onlineBankingSettings().value("provider").isEmpty()) { QPixmap ovly = DesktopIcon("download", size); pixmapPainter.drawPixmap(size / 2, size / 2, ovly.width() / 2, ovly.height() / 2, ovly); } return result; } QString MyMoneyAccount::accountTypeToString(const MyMoneyAccount::accountTypeE accountType) { QString returnString; switch (accountType) { case MyMoneyAccount::Checkings: returnString = i18n("Checking"); break; case MyMoneyAccount::Savings: returnString = i18n("Savings"); break; case MyMoneyAccount::CreditCard: returnString = i18n("Credit Card"); break; case MyMoneyAccount::Cash: returnString = i18n("Cash"); break; case MyMoneyAccount::Loan: returnString = i18n("Loan"); break; case MyMoneyAccount::CertificateDep: returnString = i18n("Certificate of Deposit"); break; case MyMoneyAccount::Investment: returnString = i18n("Investment"); break; case MyMoneyAccount::MoneyMarket: returnString = i18n("Money Market"); break; case MyMoneyAccount::Asset: returnString = i18n("Asset"); break; case MyMoneyAccount::Liability: returnString = i18n("Liability"); break; case MyMoneyAccount::Currency: returnString = i18n("Currency"); break; case MyMoneyAccount::Income: returnString = i18n("Income"); break; case MyMoneyAccount::Expense: returnString = i18n("Expense"); break; case MyMoneyAccount::AssetLoan: returnString = i18n("Investment Loan"); break; case MyMoneyAccount::Stock: returnString = i18n("Stock"); break; case MyMoneyAccount::Equity: returnString = i18n("Equity"); break; default: returnString = i18nc("Unknown account type", "Unknown"); } return returnString; } bool MyMoneyAccount::addReconciliation(const QDate& date, const MyMoneyMoney& amount) { m_reconciliationHistory[date] = amount; QString history, sep; QMap::const_iterator it; for (it = m_reconciliationHistory.constBegin(); it != m_reconciliationHistory.constEnd(); ++it) { history += QString("%1%2:%3").arg(sep, it.key().toString(Qt::ISODate), (*it).toString()); sep = QLatin1Char(';'); } setValue("reconciliationHistory", history); return true; } const QMap& MyMoneyAccount::reconciliationHistory() { // check if the internal history member is already loaded if (m_reconciliationHistory.count() == 0 && !value("reconciliationHistory").isEmpty()) { QStringList entries = value("reconciliationHistory").split(';'); foreach (const QString& entry, entries) { QStringList parts = entry.split(':'); QDate date = QDate::fromString(parts[0], Qt::ISODate); MyMoneyMoney amount(parts[1]); if (parts.count() == 2 && date.isValid()) { m_reconciliationHistory[date] = amount; } } } return m_reconciliationHistory; } /** * @todo Improve setting of country for nationalAccount */ QList< payeeIdentifier > MyMoneyAccount::payeeIdentifiers() const { QList< payeeIdentifier > list; MyMoneyFile* file = MyMoneyFile::instance(); // Iban & Bic if (!value(QLatin1String("iban")).isEmpty()) { payeeIdentifierTyped iban(new payeeIdentifiers::ibanBic); iban->setIban(value("iban")); iban->setBic(file->institution(institutionId()).value("bic")); iban->setOwnerName(file->user().name()); list.append(iban); } // National Account number if (!number().isEmpty()) { payeeIdentifierTyped national(new payeeIdentifiers::nationalAccount); national->setAccountNumber(number()); national->setBankCode(file->institution(institutionId()).sortcode()); if (file->user().state().length() == 2) national->setCountry(file->user().state()); national->setOwnerName(file->user().name()); list.append(national); } return list; } QDebug operator<<(QDebug dbg, const MyMoneyAccount &a) { - dbg << "MyMoneyAccount(" - << "id" << a.id() - << "accountType" << MyMoneyAccount::accountTypeToString(a.accountType()); - dbg = operator<<(dbg, static_cast(a)) - << ")"; - return dbg; + return DebugIndenter(dbg, a) + << "accountList " << a.accountList() + << "accountType" << MyMoneyAccount::accountTypeToString(a.accountType()) + << "balance " << a.balance() + << "brokerageName " << a.brokerageName() + << "currencyId" << a.currencyId() + << "description " << a.description() + << "fraction" << a.fraction() + << "institutionId " << a.institutionId() + << "isClosed" << a.isClosed() + << "lastModified" << a.lastModified() + << "lastReconciliationDate" << a.lastReconciliationDate() + << "name" << a.name() + << "number" << a.number() + << "onlineBankingSettings " << a.onlineBankingSettings() + << "openingDate " << a.openingDate() + << "parentAccountId " << a.parentAccountId() + //<< "reconciliationHistory " << a.reconciliationHistory() + << static_cast(a) + << static_cast(a); +} + +QDebug operator<<(QDebug dbg, const QList &a) +{ + QStringList l; + foreach(const MyMoneyAccount::accountTypeE accountType, a) + l << MyMoneyAccount::accountTypeToString(accountType); + return DebugIndenter(dbg, typeid(a).name()) << l; } diff --git a/kmymoney/mymoney/mymoneyaccount.h b/kmymoney/mymoney/mymoneyaccount.h index d0a7a1f3d..2fd2dbf20 100644 --- a/kmymoney/mymoney/mymoneyaccount.h +++ b/kmymoney/mymoney/mymoneyaccount.h @@ -1,783 +1,784 @@ /*************************************************************************** mymoneyaccount.h ------------------- copyright : (C) 2002 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. * * * ***************************************************************************/ #ifndef MYMONEYACCOUNT_H #define MYMONEYACCOUNT_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneyobject.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneysecurity.h" #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" #include "mymoneyutils.h" #include "payeeidentifier/payeeidentifier.h" #include "payeeidentifier/ibanandbic/ibanbic.h" #include "mymoneypayeeidentifiercontainer.h" class MyMoneyTransaction; class MyMoneySplit; class MyMoneyObjectContainer; /** * A representation of an account. * This object represents any type of account, those held at an * institution as well as the accounts used for double entry * accounting. * * Currently, the following account types are known: * * @li UnknownAccountType * @li Checkings * @li Savings * @li Cash * @li CreditCard * @li Loan (collected) * @li CertificateDep * @li Investment * @li MoneyMarket * @li Currency * @li Asset * @li Liability * @li Income * @li Expense * @li Loan (given) * @li Stock * @li Equity * * @see MyMoneyInstitution * @see MyMoneyFile * * @author Michael Edwardes 2000-2001 * @author Thomas Baumgart 2002 * **/ class KMM_MYMONEY_EXPORT MyMoneyAccount : public MyMoneyObject, public MyMoneyKeyValueContainer /*, public MyMoneyPayeeIdentifierContainer */ { friend class MyMoneyObjectContainer; KMM_MYMONEY_UNIT_TESTABLE public: /** * Account types currently supported. */ typedef enum _accountTypeE { UnknownAccountType = 0, /**< For error handling */ Checkings, /**< Standard checking account */ Savings, /**< Typical savings account */ Cash, /**< Denotes a shoe-box or pillowcase stuffed with cash */ CreditCard, /**< Credit card accounts */ Loan, /**< Loan and mortgage accounts (liability) */ CertificateDep, /**< Certificates of Deposit */ Investment, /**< Investment account */ MoneyMarket, /**< Money Market Account */ Asset, /**< Denotes a generic asset account.*/ Liability, /**< Denotes a generic liability account.*/ Currency, /**< Denotes a currency trading account. */ Income, /**< Denotes an income account */ Expense, /**< Denotes an expense account */ AssetLoan, /**< Denotes a loan (asset of the owner of this object) */ Stock, /**< Denotes an security account as sub-account for an investment */ Equity, /**< Denotes an equity account e.g. opening/closeing balance */ /* insert new account types above this line */ MaxAccountTypes /**< Denotes the number of different account types */ } accountTypeE; /** * This is the constructor for a new empty account */ MyMoneyAccount(); /** * This is the constructor for a new account known to the current file * This is the only constructor that will set the attribute m_openingDate * to a correct value. * * @param id id assigned to the account * @param right account definition */ MyMoneyAccount(const QString& id, const MyMoneyAccount& right); /** * This is the constructor for an account that is described by a * QDomElement (e.g. from a file). * * @param el const reference to the QDomElement from which to * create the object */ MyMoneyAccount(const QDomElement& el); /** * This is the destructor for any MyMoneyAccount object */ ~MyMoneyAccount(); /** * This operator tests for equality of two MyMoneyAccount objects */ bool operator == (const MyMoneyAccount &) const; /** * This converts the account type into one of the four * major account types liability, asset, expense or income. * * The current assignment is as follows: * * - Asset * - Asset * - Checkings * - Savings * - Cash * - Currency * - Investment * - MoneyMarket * - CertificateDep * - AssetLoan * - Stock * * - Liability * - Liability * - CreditCard * - Loan * * - Income * - Income * * - Expense * - Expense * * @return accountTypeE of major account type */ MyMoneyAccount::accountTypeE accountGroup() const; /** * This method returns the id of the MyMoneyInstitution object this account * belongs to. * @return id of MyMoneyInstitution object. QString() if it is * an internal account * @see setInstitution */ const QString& institutionId() const { return m_institution; } /** * This method returns the name of the account * @return name of account * @see setName() */ const QString& name() const { return m_name; } /** * This method returns the number of the account at the institution * @return number of account at the institution * @see setNumber */ const QString& number() const { return m_number; } /** * This method returns the descriptive text of the account. * @return description of account * @see setDescription */ const QString& description() const { return m_description; } /** * This method returns the opening date of this account * @return date of opening of this account as const QDate value * @see setOpeningDate() */ const QDate& openingDate() const { return m_openingDate; } /** * This method returns the date of the last reconciliation of this account * @return date of last reconciliation as const QDate value * @see setLastReconciliationDate */ const QDate& lastReconciliationDate() const { return m_lastReconciliationDate; } /** * This method returns the date the account was last modified * @return date of last modification as const QDate value * @see setLastModified */ const QDate& lastModified() const { return m_lastModified; } /** * This method is used to return the ID of the parent account * @return QString with the ID of the parent of this account */ const QString& parentAccountId() const { return m_parentAccount; }; /** * This method returns the list of the account id's of * subordinate accounts * @return QStringList account ids */ const QStringList& accountList() const { return m_accountList; }; /** * This method returns the number of entries in the m_accountList * @return number of entries in the accountList */ int accountCount() const { return m_accountList.count(); }; /** * This method is used to add an account id as sub-ordinate account * @param account const QString reference to account ID */ void addAccountId(const QString& account); /** * This method is used to remove an account from the list of * sub-ordinate accounts. * @param account const QString reference to account ID to be removed */ void removeAccountId(const QString& account); /** * This method is used to remove all accounts from the list of * sub-ordinate accounts. */ void removeAccountIds(); /** * This method is used to modify the date of the last * modification access. * @param date date of last modification * @see lastModified */ void setLastModified(const QDate& date); /** * This method is used to set the name of the account * @param name name of the account * @see name */ void setName(const QString& name); /** * This method is used to set the number of the account at the institution * @param number number of the account * @see number */ void setNumber(const QString& number); /** * Return the stored account identifiers * * @internal This method is temporary until MyMoneyAccount is a MyMoneyPayeeIdentifierContainer. But before this * can happen the account numbers and iban kvp data must be moved there. */ QList< payeeIdentifier > payeeIdentifiers() const; /** * @see MyMoneyPayeeIdentifierContainer::payeeIdentifiersByType() */ template< class type > QList< ::payeeIdentifierTyped > payeeIdentifiersByType() const; /** * This method is used to set the descriptive text of the account * * @param desc text that serves as description * @see setDescription */ void setDescription(const QString& desc); /** * This method is used to set the id of the institution this account * belongs to. * * @param id id of the institution this account belongs to * @see institution */ void setInstitutionId(const QString& id); /** * This method is used to set the opening date information of an * account. * * @param date QDate of opening date * @see openingDate */ void setOpeningDate(const QDate& date); /** * This method is used to set the date of the last reconciliation * of an account. * @param date QDate of last reconciliation * @see lastReconciliationDate */ void setLastReconciliationDate(const QDate& date); /** * This method is used to change the account type * * @param type account type */ void setAccountType(const accountTypeE type); /** * This method is used to set a new parent account id * @param parent QString reference to new parent account */ void setParentAccountId(const QString& parent); /** * This method is used to update m_lastModified to the current date */ void touch() { setLastModified(QDate::currentDate()); } /** * This method returns the type of the account. */ accountTypeE accountType() const { return m_accountType; } /** * This method retrieves the id of the currency used with this account. * If the return value is empty, the base currency should be used. * * @return id of currency */ const QString& currencyId() const { return m_currencyId; }; /** * This method sets the id of the currency used with this account. * * @param id ID of currency to be associated with this account. */ void setCurrencyId(const QString& id); void writeXML(QDomDocument& document, QDomElement& parent) const; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ virtual bool hasReferenceTo(const QString& id) const; /** * This member returns the balance of this account based on * all transactions stored in the journal. */ const MyMoneyMoney& balance() const { return m_balance; } /** * This method adjusts the balance of this account * according to the difference contained in the split @p s. * If the s.action() is MyMoneySplit::ActionSplitShares then * the balance will be adjusted accordingly. * * @param s const reference to MyMoneySplit object containing the * value to be added/subtracted to/from the balance * @param reverse add (false) or subtract (true) the shares contained in the split. * It also affects the balance for share splits in the opposite direction. */ void adjustBalance(const MyMoneySplit& s, bool reverse = false); /** * This method sets the balance of this account * according to the value provided by @p val. * * @param val const reference to MyMoneyMoney object containing the * value to be assigned to the balance */ void setBalance(const MyMoneyMoney& val) { m_balance = val; } /** * This method sets the kvp's for online banking with this account * * @param values The container of kvp's needed when connecting to this account */ void setOnlineBankingSettings(const MyMoneyKeyValueContainer& values); /** * This method retrieves the kvp's for online banking with this account * * @return The container of kvp's needed when connecting to this account */ const MyMoneyKeyValueContainer& onlineBankingSettings() const; /** * This method sets the closed flag for the account. This is just * an informational flag for the application. It has no other influence * on the behaviour of the account object. The default for * new objects @p open. * * @param isClosed mark the account closed (@p true) or open (@p false). */ void setClosed(bool isClosed); /** * Return the closed flag for the account. * * @retval false account is marked open (the default for new accounts) * @retval true account is marked closed */ bool isClosed() const; /** * returns the applicable smallest fraction for this account * for the given security based on the account type. At the same * time, m_fraction is updated to the value returned. * * @param sec const reference to currency (security) * * @retval sec.smallestCashFraction() for account type Cash * @retval sec.smallestAccountFraction() for all other account types */ int fraction(const MyMoneySecurity& sec); /** * Same as the above method, but does not modify m_fraction. */ int fraction(const MyMoneySecurity& sec) const; /** * This method returns the stored value for the fraction of this * account or -1 if not initialized. It can be initialized by * calling fraction(const MyMoneySecurity& sec) once. * * @note Don't use this method outside of KMyMoney application context (eg. testcases). * Use the above method instead. */ int fraction() const; /** * This method returns @a true if the account type is * either Income or Expense * * @retval true account is of type income or expense * @retval false for all other account types * * @deprecated use isIncomeExpense() instead */ KMM_MYMONEY_DEPRECATED bool isCategory() const; /** * This method returns @a true if the account type is * either Income or Expense * * @retval true account is of type income or expense * @retval false for all other account types */ bool isIncomeExpense() const; /** * This method returns @a true if the account type is * either Asset or Liability * * @retval true account is of type asset or liability * @retval false for all other account types */ bool isAssetLiability() const; /** * This method returns @a true if the account type is * either AssetLoan or Loan * * @retval true account is of type Loan or AssetLoan * @retval false for all other account types */ bool isLoan() const; /** * This method returns @a true if the account type is * Stock * * @retval true account is of type Stock * @retval false for all other account types */ bool isInvest() const; /** * This method returns @a true if the account type is * Checkings, Savings or Cash * * @retval true account is of type Checkings, Savings or Cash * @retval false for all other account types */ bool isLiquidAsset() const; /** * This method returns a name that has a brokerage suffix of * the current name. It only works on investment accounts and * returns the name for all other cases. */ QString brokerageName() const; /** * @param reconcileFlag if set to @a true a reconcile overlay will be * added to the pixmap returned * @param size is a hint for the size of the icon * @return a pixmap using DesktopIcon for the account type */ QPixmap accountPixmap(bool reconcileFlag = false, int size = 0) const; /** * This method is used to convert the internal representation of * an account type into a human readable format * * @param accountType numerical representation of the account type. * For possible values, see MyMoneyAccount::accountTypeE * @return QString representing the human readable form */ static QString accountTypeToString(const MyMoneyAccount::accountTypeE accountType); /** * keeps a history record of a reconciliation for this account on @a date * with @a amount. * * @return @p true in case entry was added, @p false otherwise * * @sa reconciliationHistory() */ bool addReconciliation(const QDate& date, const MyMoneyMoney& amount); /** * @return QMap with the reconciliation history for the account * * @sa addReconciliation() */ const QMap& reconciliationHistory(); QDataStream &operator<<(const MyMoneyAccount &); QDataStream &operator>>(MyMoneyAccount &); private: /** * This member variable identifies the type of account */ accountTypeE m_accountType; /** * This member variable keeps the ID of the MyMoneyInstitution object * that this object belongs to. */ QString m_institution; /** * This member variable keeps the name of the account * It is solely for documentation purposes and it's contents is not * used otherwise by the mymoney-engine. */ QString m_name; /** * This member variable keeps the account number at the institution * It is solely for documentation purposes and it's contents is not * used otherwise by the mymoney-engine. */ QString m_number; /** * This member variable is a description of the account. * It is solely for documentation purposes and it's contents is not * used otherwise by the mymoney-engine. */ QString m_description; /** * This member variable keeps the date when the account * was last modified. */ QDate m_lastModified; /** * This member variable keeps the date when the * account was created as an object in a MyMoneyFile */ QDate m_openingDate; /** * This member variable keeps the date of the last * reconciliation of this account */ QDate m_lastReconciliationDate; /** * This member holds the ID's of all sub-ordinate accounts */ QStringList m_accountList; /** * This member contains the ID of the parent account */ QString m_parentAccount; /** * This member contains the ID of the currency associated with this account */ QString m_currencyId; /** * This member holds the balance of all transactions stored in the journal * for this account. */ MyMoneyMoney m_balance; /** * This member variable keeps the set of kvp's needed to establish * online banking sessions to this account. */ MyMoneyKeyValueContainer m_onlineBankingSettings; /** * This member keeps the fraction for the account. It is filled by MyMoneyFile * when set to -1. See also @sa fraction(const MyMoneySecurity&). */ int m_fraction; /** * This member keeps the reconciliation history */ QMap m_reconciliationHistory; }; /** * This class is a convenience class to access data for loan accounts. * It does contain the same member variables as a MyMoneyAccount object, * but serves a set of getter/setter methods to ease the access to * laon relevant data stored in the key value container of the MyMoneyAccount * object. */ class KMM_MYMONEY_EXPORT MyMoneyAccountLoan : public MyMoneyAccount { public: enum interestDueE { paymentDue = 0, paymentReceived }; enum interestChangeUnitE { changeDaily = 0, changeWeekly, changeMonthly, changeYearly }; MyMoneyAccountLoan() {} MyMoneyAccountLoan(const MyMoneyAccount&); ~MyMoneyAccountLoan() {} const MyMoneyMoney loanAmount() const; void setLoanAmount(const MyMoneyMoney& amount); const MyMoneyMoney interestRate(const QDate& date) const; void setInterestRate(const QDate& date, const MyMoneyMoney& rate); interestDueE interestCalculation() const; void setInterestCalculation(const interestDueE onReception); const QDate nextInterestChange() const; void setNextInterestChange(const QDate& date); const QString schedule() const; void setSchedule(const QString& sched); bool fixedInterestRate() const; void setFixedInterestRate(const bool fixed); const MyMoneyMoney finalPayment() const; void setFinalPayment(const MyMoneyMoney& finalPayment); unsigned int term() const; void setTerm(const unsigned int payments); int interestChangeFrequency(int* unit = 0) const; void setInterestChangeFrequency(const int amount, const int unit); const MyMoneyMoney periodicPayment() const; void setPeriodicPayment(const MyMoneyMoney& payment); int interestCompounding() const; void setInterestCompounding(int frequency); const QString payee() const; void setPayee(const QString& payee); const QString interestAccountId() const; void setInterestAccountId(const QString& id); /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ virtual bool hasReferenceTo(const QString& id) const; }; template< class type > QList< payeeIdentifierTyped > MyMoneyAccount::payeeIdentifiersByType() const { QList< payeeIdentifierTyped > typedList; return typedList; } template<> QList< payeeIdentifierTyped< ::payeeIdentifiers::ibanBic> > MyMoneyAccount::payeeIdentifiersByType() const; /** * Make it possible to hold @ref MyMoneyAccount objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyAccount) -QDebug operator<<(QDebug dbg, const MyMoneyAccount &a); +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, const MyMoneyAccount &a); +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, const QList &a); #endif diff --git a/kmymoney/mymoney/mymoneymoney.cpp b/kmymoney/mymoney/mymoneymoney.cpp index 09c92c6c3..04e6ac669 100644 --- a/kmymoney/mymoney/mymoneymoney.cpp +++ b/kmymoney/mymoney/mymoneymoney.cpp @@ -1,341 +1,340 @@ /*************************************************************************** mymoneymymoney.cpp - description ------------------- begin : Thu Feb 21 2002 copyright : (C) 2000-2002 by Michael Edwardes (C) 2001-2011 by Thomas Baumgart (C) 2011 by Carlos Eduardo da Silva ***************************************************************************/ /*************************************************************************** * * * 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 "misc/debugindenter.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyaccount.h" #include "mymoneysecurity.h" 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) { _thousandSeparator = separator; } 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 KGlobal::locale()->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); } //////////////////////////////////////////////////////////////////////////////// // 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); } //////////////////////////////////////////////////////////////////////////////// // 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); } //////////////////////////////////////////////////////////////////////////////// // 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); } MyMoneyMoney MyMoneyMoney::convert(const signed64 _denom, const roundingMethod how) const { return 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; } QDebug operator<<(QDebug dbg, const MyMoneyMoney &a) { - dbg << "MyMoneyMoney(" - << "isAutoCalc" << a.isAutoCalc() - << "isNegative" << a.isNegative() - << "isPositive" << a.isPositive() - << "isZero" << a.isZero() - << "value" << a.toString() - << ")"; - return dbg; + return DebugIndenter(dbg, typeid(a).name()) + << "isAutoCalc" << a.isAutoCalc() + << "isNegative" << a.isNegative() + << "isPositive" << a.isPositive() + << "isZero" << a.isZero() + << "value" << a.toString(); } diff --git a/kmymoney/mymoney/mymoneymoney.h b/kmymoney/mymoney/mymoneymoney.h index 88c9ced83..45354e497 100644 --- a/kmymoney/mymoney/mymoneymoney.h +++ b/kmymoney/mymoney/mymoneymoney.h @@ -1,492 +1,492 @@ /*************************************************************************** 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 #ifdef HAVE_CONFIG_H #include #endif // #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" // Check for standard definitions #ifdef HAVE_STDINT_H #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS // force definition of min and max values #endif #include #else #include #define INT64_MAX LLONG_MAX #define INT64_MIN LLONG_MIN #endif // Including the AlkValue class #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); virtual ~MyMoneyMoney(); const MyMoneyMoney abs() const { return 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-(); } //////////////////////////////////////////////////////////////////////////////// // 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); } /** * Make it possible to hold @ref MyMoneyMoney objects * inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyMoney) -QDebug operator<<(QDebug dbg, const MyMoneyMoney &a); -QDebug operator<<(QDebug dbg, QList &a); -QDebug operator<<(QDebug dbg, QMap &a); -QDebug operator<<(QDebug dbg, QMap &a); -QDebug operator<<(QDebug dbg, QMap &a); +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, const MyMoneyMoney &a); +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, QList &a); +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, QMap &a); +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, QMap &a); +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, QMap &a); #endif diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp index 2b2c02ba1..ebfff1817 100644 --- a/kmymoney/mymoney/mymoneyreport.cpp +++ b/kmymoney/mymoney/mymoneyreport.cpp @@ -1,866 +1,916 @@ /*************************************************************************** mymoneyreport.cpp ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 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. * * * ***************************************************************************/ #include "mymoneyreport.h" #include +#include // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "reportdebug.h" const QStringList MyMoneyReport::Row::kText = QString("none,assetliability,expenseincome,category,topcategory,account,tag,payee,month,week,topaccount,topaccount-account,equitytype,accounttype,institution,budget,budgetactual,schedule,accountinfo,accountloaninfo,accountreconcile,cashflow").split(','); const MyMoneyReport::Report::Type MyMoneyReport::Report::kTypeArray[] = { NoReport, PivotTable, PivotTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, QueryTable, PivotTable, PivotTable, InfoTable, InfoTable, InfoTable, QueryTable, QueryTable, NoReport }; const QStringList MyMoneyReport::Column::kTypeText = QString("none,months,bimonths,quarters,4,5,6,weeks,8,9,10,11,years").split(','); // if you add names here, don't forget to update the bitmap for QueryColumns::Type // and shift the bit for QueryColumns::end one position to the left const QStringList MyMoneyReport::QueryColumns::kText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance").split(','); const QStringList MyMoneyReport::DetailLevel::kText = QString("none,all,top,group,total,invalid").split(','); const QStringList MyMoneyReport::Chart::kText = QString("none,line,bar,pie,ring,stackedbar,invalid").split(','); // This should live in mymoney/mymoneytransactionfilter.h const QStringList kTypeText = QString("all,payments,deposits,transfers,none").split(','); const QStringList kStateText = QString("all,notreconciled,cleared,reconciled,frozen,none").split(','); const QStringList kDateLockText = QString("alldates,untiltoday,currentmonth,currentyear,monthtodate,yeartodate,yeartomonth,lastmonth,lastyear,last7days,last30days,last3months,last6months,last12months,next7days,next30days,next3months,next6months,next12months,userdefined,last3tonext3months,last11Months,currentQuarter,lastQuarter,nextQuarter,currentFiscalYear,lastFiscalYear,today,next18months").split(','); const QStringList kAccountTypeText = QString("unknown,checkings,savings,cash,creditcard,loan,certificatedep,investment,moneymarket,asset,liability,currency,income,expense,assetloan,stock,equity,invalid").split(','); QString MyMoneyReport::Row::toString(Type type) { switch(type) { case NoRows : return "NoRows"; case AssetLiability : return "AssetLiability"; case ExpenseIncome : return "ExpenseIncome"; case Category : return "Category"; case TopCategory : return "TopCategory"; case Account : return "Account"; case Tag : return "Tag"; case Payee : return "Payee"; case Month : return "Month"; case Week : return "Week"; case TopAccount : return "TopAccount"; case AccountByTopAccount: return "AccountByTopAccount"; case EquityType : return "EquityType"; case AccountType : return "AccountType"; case Institution : return "Institution"; case Budget : return "Budget"; case BudgetActual : return "BudgetActual"; case Schedule : return "Schedule"; case AccountInfo : return "AccountInfo"; case AccountLoanInfo : return "AccountLoanInfo"; case AccountReconcile : return "AccountReconcile"; case CashFlow : return "CashFlow"; default : return "undefined"; } } QString MyMoneyReport::Report::toString(Type type) { switch(type) { case NoReport: return "NoReport"; case PivotTable: return "PivotTable"; case QueryTable: return "QueryTable"; case InfoTable: return "InfoTable"; default: return "undefined"; } } MyMoneyReport::MyMoneyReport() : m_name("Unconfigured Pivot Table Report"), m_detailLevel(DetailLevel::None), m_convertCurrency(true), m_favorite(false), m_tax(false), m_investments(false), m_loans(false), m_reportType(Report::kTypeArray[Row::ExpenseIncome]), m_rowType(Row::ExpenseIncome), m_columnType(Column::Months), m_columnsAreDays(false), m_queryColumns(QueryColumns::None), m_dateLock(userDefined), m_accountGroupFilter(false), m_chartType(Chart::Line), m_chartDataLabels(true), m_chartGridLines(true), m_chartByDefault(false), m_includeSchedules(false), m_includeTransfers(false), m_includeBudgetActuals(false), m_includeUnusedAccounts(false), m_showRowTotals(false), m_includeForecast(false), m_includeMovingAverage(false), m_movingAverageDays(0), m_includePrice(false), m_includeAveragePrice(false), m_mixedTime(false), m_currentDateColumn(0), m_skipZero(false) { m_chartLineWidth = m_lineWidth; } MyMoneyReport::MyMoneyReport(const QString& id, const MyMoneyReport& right) : MyMoneyObject(id), m_movingAverageDays(0), m_currentDateColumn(0) { *this = right; setId(id); } MyMoneyReport::MyMoneyReport(Row::Type _rt, unsigned _ct, dateOptionE _dl, DetailLevel::Type _ss, const QString& _name, const QString& _comment) : m_name(_name), m_comment(_comment), m_detailLevel(_ss), m_convertCurrency(true), m_favorite(false), m_tax(false), m_investments(false), m_loans(false), m_reportType(Report::kTypeArray[_rt]), m_rowType(_rt), m_columnType(Column::Months), m_columnsAreDays(false), m_queryColumns(QueryColumns::None), m_dateLock(_dl), m_accountGroupFilter(false), m_chartType(Chart::Line), m_chartDataLabels(true), m_chartGridLines(true), m_chartByDefault(false), m_includeSchedules(false), m_includeTransfers(false), m_includeBudgetActuals(false), m_includeUnusedAccounts(false), m_showRowTotals(false), m_includeForecast(false), m_includeMovingAverage(false), m_movingAverageDays(0), m_includePrice(false), m_includeAveragePrice(false), m_mixedTime(false), m_currentDateColumn(0), m_skipZero(false) { //set initial values m_chartLineWidth = m_lineWidth; //set report type if (m_reportType == Report::PivotTable) m_columnType = static_cast(_ct); if (m_reportType == Report::QueryTable) m_queryColumns = static_cast(_ct); setDateFilter(_dl); //throw exception if the type is inconsistent if ((_rt > static_cast(sizeof(Report::kTypeArray) / sizeof(Report::kTypeArray[0]))) || (m_reportType == Report::NoReport)) throw MYMONEYEXCEPTION("Invalid report type"); //add the corresponding account groups if (_rt == MyMoneyReport::Row::AssetLiability) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); m_showRowTotals = true; } if (_rt == MyMoneyReport::Row::Account) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::AssetLoan); addAccountGroup(MyMoneyAccount::Cash); addAccountGroup(MyMoneyAccount::Checkings); addAccountGroup(MyMoneyAccount::CreditCard); if (KMyMoneyGlobalSettings::expertMode()) addAccountGroup(MyMoneyAccount::Equity); addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); addAccountGroup(MyMoneyAccount::Liability); addAccountGroup(MyMoneyAccount::Loan); addAccountGroup(MyMoneyAccount::Savings); addAccountGroup(MyMoneyAccount::Stock); m_showRowTotals = true; } if (_rt == MyMoneyReport::Row::ExpenseIncome) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); m_showRowTotals = true; } //FIXME take this out once we have sorted out all issues regarding budget of assets and liabilities -- asoliverez@gmail.com if (_rt == MyMoneyReport::Row::Budget || _rt == MyMoneyReport::Row::BudgetActual) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); } if (_rt == MyMoneyReport::Row::AccountInfo) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } //cash flow reports show splits for all account groups if (_rt == MyMoneyReport::Row::CashFlow) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } #ifdef DEBUG_REPORTS QDebug dbg = qDebug(); dbg << _name << Row::toString(_rt) << Report::toString(m_reportType); foreach(const MyMoneyAccount::accountTypeE accountType, m_accountGroups) dbg << MyMoneyAccount::accountTypeToString(accountType); if (m_accounts.size() > 0) dbg << m_accounts; #endif } MyMoneyReport::MyMoneyReport(const QDomElement& node) : MyMoneyObject(node), m_currentDateColumn(0) { // properly initialize the object before reading it *this = MyMoneyReport(); if (!read(node)) clearId(); } void MyMoneyReport::clear() { m_accountGroupFilter = false; m_accountGroups.clear(); MyMoneyTransactionFilter::clear(); } void MyMoneyReport::validDateRange(QDate& _db, QDate& _de) { _db = fromDate(); _de = toDate(); // if either begin or end date are invalid we have one of the following // possible date filters: // // a) begin date not set - first transaction until given end date // b) end date not set - from given date until last transaction // c) both not set - first transaction until last transaction // // If there is no transaction in the engine at all, we use the current // year as the filter criteria. if (!_db.isValid() || !_de.isValid()) { QList list = MyMoneyFile::instance()->transactionList(*this); QDate tmpBegin, tmpEnd; if (!list.isEmpty()) { qSort(list); // try to use the post dates tmpBegin = list.front().postDate(); tmpEnd = list.back().postDate(); // if the post dates are not valid try the entry dates if (!tmpBegin.isValid()) tmpBegin = list.front().entryDate(); if (!tmpEnd.isValid()) tmpEnd = list.back().entryDate(); } // make sure that we leave this function with valid dates no mather what if (!tmpBegin.isValid() || !tmpEnd.isValid() || tmpBegin > tmpEnd) { tmpBegin = QDate(QDate::currentDate().year(), 1, 1); // the first date in the file tmpEnd = QDate(QDate::currentDate().year(), 12, 31); // the last date in the file } if (!_db.isValid()) _db = tmpBegin; if (!_de.isValid()) _de = tmpEnd; } if (_db > _de) _db = _de; } void MyMoneyReport::setRowType(Row::Type _rt) { m_rowType = _rt; m_reportType = Report::kTypeArray[_rt]; m_accountGroupFilter = false; m_accountGroups.clear(); if (_rt == MyMoneyReport::Row::AssetLiability) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } if (_rt == MyMoneyReport::Row::ExpenseIncome) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); } } bool MyMoneyReport::accountGroups(QList& list) const { bool result = m_accountGroupFilter; if (result) { QList::const_iterator it_group = m_accountGroups.begin(); while (it_group != m_accountGroups.end()) { list += (*it_group); ++it_group; } } return result; } void MyMoneyReport::addAccountGroup(MyMoneyAccount::accountTypeE type) { if (!m_accountGroups.isEmpty() && type != MyMoneyAccount::UnknownAccountType) { if (m_accountGroups.contains(type)) return; } m_accountGroupFilter = true; if (type != MyMoneyAccount::UnknownAccountType) m_accountGroups.push_back(type); } bool MyMoneyReport::includesAccountGroup(MyMoneyAccount::accountTypeE type) const { bool result = (! m_accountGroupFilter) || (isIncludingTransfers() && m_rowType == MyMoneyReport::Row::ExpenseIncome) || m_accountGroups.contains(type); return result; } bool MyMoneyReport::includes(const MyMoneyAccount& acc) const { bool result = false; if (includesAccountGroup(acc.accountGroup())) { switch (acc.accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: if (isTax()) result = (acc.value("Tax") == "Yes") && includesCategory(acc.id()); else result = includesCategory(acc.id()); break; case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: if (isLoansOnly()) result = acc.isLoan() && includesAccount(acc.id()); else if (isInvestmentsOnly()) result = acc.isInvest() && includesAccount(acc.id()); else if (isIncludingTransfers() && m_rowType == MyMoneyReport::Row::ExpenseIncome) // If transfers are included, ONLY include this account if it is NOT // included in the report itself!! result = ! includesAccount(acc.id()); else result = includesAccount(acc.id()); break; default: result = includesAccount(acc.id()); } } return result; } void MyMoneyReport::write(QDomElement& e, QDomDocument *doc, bool anonymous) const { // No matter what changes, be sure to have a 'type' attribute. Only change // the major type if it becomes impossible to maintain compatibility with // older versions of the program as new features are added to the reports. // Feel free to change the minor type every time a change is made here. writeBaseXML(*doc, e); if (anonymous) { e.setAttribute("name", m_id); e.setAttribute("comment", QString(m_comment).fill('x')); } else { e.setAttribute("name", m_name); e.setAttribute("comment", m_comment); } e.setAttribute("group", m_group); e.setAttribute("convertcurrency", m_convertCurrency); e.setAttribute("favorite", m_favorite); e.setAttribute("tax", m_tax); e.setAttribute("investments", m_investments); e.setAttribute("loans", m_loans); e.setAttribute("rowtype", Row::kText[m_rowType]); e.setAttribute("datelock", kDateLockText[m_dateLock]); e.setAttribute("includeschedules", m_includeSchedules); e.setAttribute("columnsaredays", m_columnsAreDays); e.setAttribute("includestransfers", m_includeTransfers); if (!m_budgetId.isEmpty()) e.setAttribute("budget", m_budgetId); e.setAttribute("includesactuals", m_includeBudgetActuals); e.setAttribute("includeunused", m_includeUnusedAccounts); e.setAttribute("includesforecast", m_includeForecast); e.setAttribute("includesprice", m_includePrice); e.setAttribute("includesaverageprice", m_includeAveragePrice); e.setAttribute("mixedtime", m_mixedTime); e.setAttribute("includesmovingaverage", m_includeMovingAverage); if (m_includeMovingAverage) e.setAttribute("movingaveragedays", m_movingAverageDays); if (m_chartType < 0 || m_chartType >= Chart::kText.size()) { qDebug("m_chartType out of bounds with %d on report of type %d. Default to none.", m_chartType, m_reportType); e.setAttribute("charttype", Chart::kText[0]); } else { e.setAttribute("charttype", Chart::kText[m_chartType]); } e.setAttribute("chartdatalabels", m_chartDataLabels); e.setAttribute("chartgridlines", m_chartGridLines); e.setAttribute("chartbydefault", m_chartByDefault); e.setAttribute("chartlinewidth", m_chartLineWidth); e.setAttribute("skipZero", m_skipZero); if (m_reportType == Report::PivotTable) { e.setAttribute("type", "pivottable 1.15"); e.setAttribute("detail", DetailLevel::kText[m_detailLevel]); e.setAttribute("columntype", Column::kTypeText[m_columnType]); e.setAttribute("showrowtotals", m_showRowTotals); } else if (m_reportType == Report::QueryTable) { e.setAttribute("type", "querytable 1.14"); QStringList columns; unsigned qc = m_queryColumns; unsigned it_qc = QueryColumns::Begin; unsigned index = 1; while (it_qc != QueryColumns::End) { if (qc & it_qc) columns += QueryColumns::kText[index]; it_qc *= 2; index++; } e.setAttribute("querycolumns", columns.join(",")); } else if (m_reportType == Report::InfoTable) { e.setAttribute("type", "infotable 1.0"); e.setAttribute("detail", DetailLevel::kText[m_detailLevel]); e.setAttribute("showrowtotals", m_showRowTotals); } // // Text Filter // QRegExp textfilter; if (textFilter(textfilter)) { QDomElement f = doc->createElement("TEXT"); f.setAttribute("pattern", textfilter.pattern()); f.setAttribute("casesensitive", (textfilter.caseSensitivity() == Qt::CaseSensitive) ? 1 : 0); f.setAttribute("regex", (textfilter.patternSyntax() == QRegExp::Wildcard) ? 1 : 0); f.setAttribute("inverttext", m_invertText); e.appendChild(f); } // // Type & State Filters // QList typelist; if (types(typelist) && ! typelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_type = typelist.constBegin(); while (it_type != typelist.constEnd()) { QDomElement p = doc->createElement("TYPE"); p.setAttribute("type", kTypeText[*it_type]); e.appendChild(p); ++it_type; } } QList statelist; if (states(statelist) && ! statelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_state = statelist.constBegin(); while (it_state != statelist.constEnd()) { QDomElement p = doc->createElement("STATE"); p.setAttribute("state", kStateText[*it_state]); e.appendChild(p); ++it_state; } } // // Number Filter // QString nrFrom, nrTo; if (numberFilter(nrFrom, nrTo)) { QDomElement f = doc->createElement("NUMBER"); f.setAttribute("from", nrFrom); f.setAttribute("to", nrTo); e.appendChild(f); } // // Amount Filter // MyMoneyMoney from, to; if (amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&); QDomElement f = doc->createElement("AMOUNT"); f.setAttribute("from", from.toString()); f.setAttribute("to", to.toString()); e.appendChild(f); } // // Payees Filter // QStringList payeelist; if (payees(payeelist)) { if (payeelist.empty()) { QDomElement p = doc->createElement("PAYEE"); e.appendChild(p); } else { // iterate over payees, and add each one QStringList::const_iterator it_payee = payeelist.constBegin(); while (it_payee != payeelist.constEnd()) { QDomElement p = doc->createElement("PAYEE"); p.setAttribute("id", *it_payee); e.appendChild(p); ++it_payee; } } } // // Tags Filter // QStringList taglist; if (tags(taglist)) { if (taglist.empty()) { QDomElement p = doc->createElement("TAG"); e.appendChild(p); } else { // iterate over tags, and add each one QStringList::const_iterator it_tag = taglist.constBegin(); while (it_tag != taglist.constEnd()) { QDomElement p = doc->createElement("TAG"); p.setAttribute("id", *it_tag); e.appendChild(p); ++it_tag; } } } // // Account Groups Filter // QList accountgrouplist; if (accountGroups(accountgrouplist)) { // iterate over accounts, and add each one QList::const_iterator it_group = accountgrouplist.constBegin(); while (it_group != accountgrouplist.constEnd()) { QDomElement p = doc->createElement("ACCOUNTGROUP"); p.setAttribute("group", kAccountTypeText[*it_group]); e.appendChild(p); ++it_group; } } // // Accounts Filter // QStringList accountlist; if (accounts(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement("ACCOUNT"); p.setAttribute("id", *it_account); e.appendChild(p); ++it_account; } } // // Categories Filter // accountlist.clear(); if (categories(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement("CATEGORY"); p.setAttribute("id", *it_account); e.appendChild(p); ++it_account; } } // // Date Filter // if (m_dateLock == userDefined) { QDate dateFrom, dateTo; if (dateFilter(dateFrom, dateTo)) { QDomElement f = doc->createElement("DATES"); if (dateFrom.isValid()) f.setAttribute("from", dateFrom.toString(Qt::ISODate)); if (dateTo.isValid()) f.setAttribute("to", dateTo.toString(Qt::ISODate)); e.appendChild(f); } } } bool MyMoneyReport::read(const QDomElement& e) { // The goal of this reading method is 100% backward AND 100% forward // compatibility. Any report ever created with any version of KMyMoney // should be able to be loaded by this method (as long as it's one of the // report types supported in this version, of course) bool result = false; if ( "REPORT" == e.tagName() && ( (e.attribute("type").indexOf("pivottable 1.") == 0) || (e.attribute("type").indexOf("querytable 1.") == 0) || (e.attribute("type").indexOf("infotable 1.") == 0) ) ) { result = true; clear(); int i; m_name = e.attribute("name"); m_comment = e.attribute("comment", "Extremely old report"); //set report type if (!e.attribute("type").indexOf("pivottable")) { m_reportType = MyMoneyReport::Report::PivotTable; } else if (!e.attribute("type").indexOf("querytable")) { m_reportType = MyMoneyReport::Report::QueryTable; } else if (!e.attribute("type").indexOf("infotable")) { m_reportType = MyMoneyReport::Report::InfoTable; } else { m_reportType = MyMoneyReport::Report::NoReport; } // Removed the line that screened out loading reports that are called // "Default Report". It's possible for the user to change the comment // to this, and we'd hate for it to break as a result. m_group = e.attribute("group"); m_id = e.attribute("id"); //check for reports with older settings which didn't have the detail attribute if (e.hasAttribute("detail")) { i = DetailLevel::kText.indexOf(e.attribute("detail", "all")); if (i != -1) m_detailLevel = static_cast(i); } else if (e.attribute("showsubaccounts", "0").toUInt()) { //set to show all accounts m_detailLevel = DetailLevel::All; } else { //set to show the top level account instead m_detailLevel = DetailLevel::Top; } m_convertCurrency = e.attribute("convertcurrency", "1").toUInt(); m_favorite = e.attribute("favorite", "0").toUInt(); m_tax = e.attribute("tax", "0").toUInt(); m_investments = e.attribute("investments", "0").toUInt(); m_loans = e.attribute("loans", "0").toUInt(); m_includeSchedules = e.attribute("includeschedules", "0").toUInt(); m_columnsAreDays = e.attribute("columnsaredays", "0").toUInt(); m_includeTransfers = e.attribute("includestransfers", "0").toUInt(); if (e.hasAttribute("budget")) m_budgetId = e.attribute("budget"); m_includeBudgetActuals = e.attribute("includesactuals", "0").toUInt(); m_includeUnusedAccounts = e.attribute("includeunused", "0").toUInt(); m_includeForecast = e.attribute("includesforecast", "0").toUInt(); m_includePrice = e.attribute("includesprice", "0").toUInt(); m_includeAveragePrice = e.attribute("includesaverageprice", "0").toUInt(); m_mixedTime = e.attribute("mixedtime", "0").toUInt(); m_includeMovingAverage = e.attribute("includesmovingaverage", "0").toUInt(); m_skipZero = e.attribute("skipZero", "0").toUInt(); if (m_includeMovingAverage) m_movingAverageDays = e.attribute("movingaveragedays", "1").toUInt(); //only load chart data if it is a pivot table m_chartType = static_cast(0); if (m_reportType == Report::PivotTable) { i = Chart::kText.indexOf(e.attribute("charttype")); if (i >= 0) m_chartType = static_cast(i); // if it is invalid, set to first type if (m_chartType >= Chart::End) m_chartType = Chart::Line; m_chartDataLabels = e.attribute("chartdatalabels", "1").toUInt(); m_chartGridLines = e.attribute("chartgridlines", "1").toUInt(); m_chartByDefault = e.attribute("chartbydefault", "0").toUInt(); m_chartLineWidth = e.attribute("chartlinewidth", QString(m_lineWidth)).toUInt(); } else { m_chartDataLabels = true; m_chartGridLines = true; m_chartByDefault = false; m_chartLineWidth = 1; } QString datelockstr = e.attribute("datelock", "userdefined"); // Handle the pivot 1.2/query 1.1 case where the values were saved as // numbers bool ok = false; i = datelockstr.toUInt(&ok); if (!ok) { i = kDateLockText.indexOf(datelockstr); if (i == -1) i = userDefined; } setDateFilter(static_cast(i)); i = Row::kText.indexOf(e.attribute("rowtype", "expenseincome")); if (i != -1) { setRowType(static_cast(i)); // recent versions of KMyMoney always showed a total column for // income/expense reports. We turn it on for backward compatibility // here. If the total column is turned off, the flag will be reset // in the next step if (i == Row::ExpenseIncome) m_showRowTotals = true; } if (e.hasAttribute("showrowtotals")) m_showRowTotals = e.attribute("showrowtotals").toUInt(); i = Column::kTypeText.indexOf(e.attribute("columntype", "months")); if (i != -1) setColumnType(static_cast(i)); unsigned qc = 0; QStringList columns = e.attribute("querycolumns", "none").split(','); QStringList::const_iterator it_column = columns.constBegin(); while (it_column != columns.constEnd()) { i = QueryColumns::kText.indexOf(*it_column); if (i > 0) qc |= (1 << (i - 1)); ++it_column; } setQueryColumns(static_cast(qc)); QDomNode child = e.firstChild(); while (!child.isNull() && child.isElement()) { QDomElement c = child.toElement(); if ("TEXT" == c.tagName() && c.hasAttribute("pattern")) { setTextFilter(QRegExp(c.attribute("pattern"), c.attribute("casesensitive", "1").toUInt() ? Qt::CaseSensitive : Qt::CaseInsensitive, c.attribute("regex", "1").toUInt() ? QRegExp::Wildcard : QRegExp::RegExp), c.attribute("inverttext", "0").toUInt()); } if ("TYPE" == c.tagName() && c.hasAttribute("type")) { i = kTypeText.indexOf(c.attribute("type")); if (i != -1) addType(i); } if ("STATE" == c.tagName() && c.hasAttribute("state")) { i = kStateText.indexOf(c.attribute("state")); if (i != -1) addState(i); } if ("NUMBER" == c.tagName()) { setNumberFilter(c.attribute("from"), c.attribute("to")); } if ("AMOUNT" == c.tagName()) { setAmountFilter(MyMoneyMoney(c.attribute("from", "0/100")), MyMoneyMoney(c.attribute("to", "0/100"))); } if ("DATES" == c.tagName()) { QDate from, to; if (c.hasAttribute("from")) from = QDate::fromString(c.attribute("from"), Qt::ISODate); if (c.hasAttribute("to")) to = QDate::fromString(c.attribute("to"), Qt::ISODate); MyMoneyTransactionFilter::setDateFilter(from, to); } if ("PAYEE" == c.tagName()) { addPayee(c.attribute("id")); } if ("TAG" == c.tagName()) { addTag(c.attribute("id")); } if ("CATEGORY" == c.tagName() && c.hasAttribute("id")) { addCategory(c.attribute("id")); } if ("ACCOUNT" == c.tagName() && c.hasAttribute("id")) { addAccount(c.attribute("id")); } if ("ACCOUNTGROUP" == c.tagName() && c.hasAttribute("group")) { i = kAccountTypeText.indexOf(c.attribute("group")); if (i != -1) addAccountGroup(static_cast(i)); } child = child.nextSibling(); } } return result; } void MyMoneyReport::writeXML(QDomDocument& document, QDomElement& parent) const { QDomElement el = document.createElement("REPORT"); write(el, &document, false); parent.appendChild(el); } bool MyMoneyReport::hasReferenceTo(const QString& id) const { QStringList list; // collect all ids accounts(list); categories(list); payees(list); tags(list); return (list.contains(id) > 0); } int MyMoneyReport::m_lineWidth = 2; void MyMoneyReport::setLineWidth(int width) { m_lineWidth = width; } + +QDebug operator<<(QDebug dbg, const MyMoneyReport &a) +{ + QList list; + a.accountGroups(list); + return DebugIndenter(dbg, a) + << "accountGroups" << list + << "budget" << a.budget() + << "chartLineWidth" << a.chartLineWidth() + << "chartType" << a.chartType() + << "columnPitch" << a.columnPitch() + << "columnType" << a.columnType() + << "comment" << a.comment() + << "currentDateColumn" << a.currentDateColumn() + << "detailLevel" << a.detailLevel() + << "name" << a.name() + << "group" << a.group() + << "hasBudget" << a.hasBudget() + << "id" << a.id() + << "isConvertCurrency" << a.isConvertCurrency() + << "isFavorite" << a.isFavorite() + << "isInvestmentsOnly" << a.isInvestmentsOnly() + << "isChartDataLabels" << a.isChartDataLabels() + << "isChartGridLines" << a.isChartGridLines() + << "isChartByDefault" << a.isChartByDefault() + << "isColumnsAreDays" << a.isColumnsAreDays() + << "isIncludingAveragePrice" << a.isIncludingAveragePrice() + << "isIncludingBudgetActuals" << a.isIncludingBudgetActuals() + << "isIncludingForecast" << a.isIncludingForecast() + << "isIncludingMovingAverage" << a.isIncludingMovingAverage() + << "isIncludingPrice" << a.isIncludingPrice() + << "isIncludingSchedules" << a.isIncludingSchedules() + << "isIncludingTransfers" << a.isIncludingTransfers() + << "isIncludingUnusedAccounts" << a.isIncludingUnusedAccounts() + << "isLoansOnly" << a.isLoansOnly() + << "isMixedTime" << a.isMixedTime() + << "isRunningSum" << a.isRunningSum() + << "isSkippingZero" << a.isSkippingZero() + << "isShowingColumnTotals" << a.isShowingColumnTotals() + << "isShowingRowTotals" << a.isShowingRowTotals() + << "isTax" << a.isTax() + << "isUserDefined" << a.isUserDefined() + << "queryColumns" << a.queryColumns() + << "movingAverageDays" << a.movingAverageDays() + << "rowType" << MyMoneyReport::Row::toString(a.rowType()) + << "reportType" << MyMoneyReport::Report::toString(a.reportType()) + // TODO Transactionfilter + ; +} diff --git a/kmymoney/mymoney/mymoneyreport.h b/kmymoney/mymoney/mymoneyreport.h index 9be413e8f..620e1c6ff 100644 --- a/kmymoney/mymoney/mymoneyreport.h +++ b/kmymoney/mymoney/mymoneyreport.h @@ -1,731 +1,732 @@ /*************************************************************************** mymoneyreport.h ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 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 MYMONEYREPORT_H #define MYMONEYREPORT_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include class QDomElement; class QDomDocument; // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include /** * This class defines a report within the MyMoneyEngine. The report class * contains all the configuration parameters needed to run a report, plus * XML serialization. * * A report is a transactionfilter, so any report can specify which * transactions it's interested down to the most minute level of detail. * It extends the transactionfilter by providing identification (name, * comments, group type, etc) as well as layout information (what kind * of layout should be used, how the rows & columns should be presented, * currency converted, etc.) * * As noted above, this class only provides a report DEFINITION. The * generation and presentation of the report itself are left to higher * level classes. * * @author Ace Jones */ class KMM_MYMONEY_EXPORT MyMoneyReport: public MyMoneyObject, public MyMoneyTransactionFilter { public: // When adding a new row type, be sure to add a corresponding entry in kTypeArray class Row { public: enum Type { NoRows = 0, AssetLiability, ExpenseIncome, Category, TopCategory, Account, Tag, Payee, Month, Week, TopAccount, AccountByTopAccount, EquityType, AccountType, Institution, Budget, BudgetActual, Schedule, AccountInfo, AccountLoanInfo, AccountReconcile, CashFlow}; /** * Return row type as string. * * @param type type to get string for * @return row type converted to string */ static QString toString(Type type); static const QStringList kText; }; class Report { public: enum Type { NoReport = 0, PivotTable, QueryTable, InfoTable }; /** * Return report type as string. * * @param type report type to get string for * @return report type converted to string */ static QString toString(Type type); static const Type kTypeArray[]; }; class Column { public: enum Type { NoColumns = 0, Days = 1, Months = 1, BiMonths = 2, Quarters = 3, Weeks = 7, Years = 12 }; static const QStringList kTypeText; }; // if you add bits to this bitmask, start with the value currently assigned to end and update its value afterwards // also don't forget to add column names to QueryColumns::kText in mymoneyreport.cpp class QueryColumns { public: enum Type { None = 0x0, Begin = 0x1, Number = 0x1, Payee = 0x2, Category = 0x4, Tag = 0x8, Memo = 0x10, Account = 0x20, Reconciled = 0x40, Action = 0x80, Shares = 0x100, Price = 0x200, Performance = 0x400, Loan = 0x800, Balance = 0x1000, End = 0x2000 }; static const QStringList kText; }; class DetailLevel { public: enum Type { None = 0, All, Top, Group, Total, End }; static const QStringList kText; }; class Chart { public: enum Type { None = 0, Line, Bar, Pie, Ring, StackedBar, End }; static const QStringList kText; }; public: MyMoneyReport(); MyMoneyReport(Row::Type _rt, unsigned _ct, dateOptionE _dl, DetailLevel::Type _ss, const QString& _name, const QString& _comment); MyMoneyReport(const QString& id, const MyMoneyReport& right); /** * This constructor creates an object based on the data found in the * QDomElement referenced by @p node. If problems arise, the @p id of * the object is cleared (see MyMoneyObject::clearId()). */ MyMoneyReport(const QDomElement& node); // Simple get operations const QString& name() const { return m_name; } bool isShowingRowTotals() const { return (m_showRowTotals); } Report::Type reportType() const { return m_reportType; } Row::Type rowType() const { return m_rowType; } Column::Type columnType() const { return m_columnType; } bool isRunningSum() const { return (m_rowType == Row::AssetLiability); } bool isConvertCurrency() const { return m_convertCurrency; } unsigned columnPitch() const { return static_cast(m_columnType); } bool isShowingColumnTotals() const { return m_convertCurrency; } const QString& comment() const { return m_comment; } QueryColumns::Type queryColumns() const { return m_queryColumns; } const QString& group() const { return m_group; } bool isFavorite() const { return m_favorite; } bool isTax() const { return m_tax; } bool isInvestmentsOnly() const { return m_investments; } bool isLoansOnly() const { return m_loans; } DetailLevel::Type detailLevel() const { return m_detailLevel; } Chart::Type chartType() const { return m_chartType; } bool isChartDataLabels() const { return m_chartDataLabels; } bool isChartGridLines() const { return m_chartGridLines; } bool isChartByDefault() const { return m_chartByDefault; } uint chartLineWidth() const { return m_chartLineWidth; } bool isIncludingSchedules() const { return m_includeSchedules; } bool isColumnsAreDays() const { return m_columnsAreDays; } bool isIncludingTransfers() const { return m_includeTransfers; } bool isIncludingUnusedAccounts() const { return m_includeUnusedAccounts; } bool hasBudget() const { return !m_budgetId.isEmpty(); } const QString& budget() const { return m_budgetId; } bool isIncludingBudgetActuals() const { return m_includeBudgetActuals; } bool isIncludingForecast() const { return m_includeForecast; } bool isIncludingMovingAverage() const { return m_includeMovingAverage; } int movingAverageDays() const { return m_movingAverageDays; } bool isIncludingPrice() const { return m_includePrice; } bool isIncludingAveragePrice() const { return m_includeAveragePrice; } bool isUserDefined() const { return m_dateLock == userDefined; } bool isMixedTime() const { return m_mixedTime; } int currentDateColumn() const { return m_currentDateColumn; } /** * @see #m_skipZero */ bool isSkippingZero() const { return m_skipZero; } // Simple set operations void setName(const QString& _s) { m_name = _s; } void setConvertCurrency(bool _f) { m_convertCurrency = _f; } void setRowType(Row::Type _rt); void setColumnType(Column::Type _ct) { m_columnType = _ct; } void setComment(const QString& _comment) { m_comment = _comment; } void setGroup(const QString& _group) { m_group = _group; } void setFavorite(bool _f) { m_favorite = _f; } void setQueryColumns(QueryColumns::Type _qc) { m_queryColumns = _qc; } void setTax(bool _f) { m_tax = _f; } void setInvestmentsOnly(bool _f) { m_investments = _f; if (_f) m_loans = false; } void setLoansOnly(bool _f) { m_loans = _f; if (_f) m_investments = false; } void setDetailLevel(DetailLevel::Type _detail) { m_detailLevel = _detail; } void setChartType(Chart::Type _type) { m_chartType = _type; } void setChartDataLabels(bool _f) { m_chartDataLabels = _f; } void setChartGridLines(bool _f) { m_chartGridLines = _f; } void setChartByDefault(bool _f) { m_chartByDefault = _f; } void setChartLineWidth(uint _f) { m_chartLineWidth = _f; } void setIncludingSchedules(bool _f) { m_includeSchedules = _f; } void setColumnsAreDays(bool _f) { m_columnsAreDays = _f; } void setIncludingTransfers(bool _f) { m_includeTransfers = _f; } void setIncludingUnusedAccounts(bool _f) { m_includeUnusedAccounts = _f; } void setShowingRowTotals(bool _f) { m_showRowTotals = _f; } void setIncludingBudgetActuals(bool _f) { m_includeBudgetActuals = _f; } void setIncludingForecast(bool _f) { m_includeForecast = _f; } void setIncludingMovingAverage(bool _f) { m_includeMovingAverage = _f; } void setMovingAverageDays(int _days) { m_movingAverageDays = _days; } void setIncludingPrice(bool _f) { m_includePrice = _f; } void setIncludingAveragePrice(bool _f) { m_includeAveragePrice = _f; } void setMixedTime(bool _f) { m_mixedTime = _f; } void setCurrentDateColumn(int _f) { m_currentDateColumn = _f; } /** * @see #m_skipZero */ void setSkipZero(int _f) { m_skipZero = _f; } /** * Sets the budget used for this report * * @param _budget The ID of the budget to use, or an empty string * to indicate a budget is NOT included * @param _fa Whether to display actual data alongside the budget. * Setting to false means the report displays ONLY the budget itself. * @warning For now, the budget ID is ignored. The budget id is * simply checked for any non-empty string, and if so, hasBudget() * will return true. */ void setBudget(const QString& _budget, bool _fa = true) { m_budgetId = _budget; m_includeBudgetActuals = _fa; } /** * This method allows you to clear the underlying transaction filter */ void clear(); /** * This method allows you to set the underlying transaction filter * * @param _filter The filter which should replace the existing transaction * filter. */ void assignFilter(const MyMoneyTransactionFilter& _filter) { MyMoneyTransactionFilter::operator=(_filter); } /** * Set the underlying date filter and LOCK that filter to the specified * range. For example, if @p _u is "CurrentMonth", this report should always * be updated to the current month no matter when the report is run. * * This updating is not entirely automatic, you should update it yourself by * calling updateDateFilter. * * @param _u The date range constant (MyMoneyTransactionFilter::dateRangeE) * which this report should be locked to. */ void setDateFilter(dateOptionE _u) { m_dateLock = _u; if (_u != userDefined) MyMoneyTransactionFilter::setDateFilter(_u); } /** * Set the underlying date filter using the start and end dates provided. * Note that this does not LOCK to any range like setDateFilter(unsigned) * above. It is just a reimplementation of the MyMoneyTransactionFilter * version. * * @param _db The inclusive begin date of the date range * @param _de The inclusive end date of the date range */ void setDateFilter(const QDate& _db, const QDate& _de) { MyMoneyTransactionFilter::setDateFilter(_db, _de); } /** * Set the underlying date filter using the 'date lock' property. * * Always call this function before executing the report to be sure that * the date filters properly match the plain-language 'date lock'. * * For example, if the report is date-locked to "Current Month", and the * last time you loaded or ran the report was in August, but it's now * September, this function will update the date range to be September, * as is proper. */ void updateDateFilter() { if (m_dateLock != userDefined) MyMoneyTransactionFilter::setDateFilter(m_dateLock); } /** * Retrieves a VALID beginning & ending date for this report. * * The underlying date filter can return en empty QDate() for either the * begin or end date or both. This is typically unacceptable for reports, * which need the REAL begin and end date. * * This function gets the underlying date filter range, and if either is * an empty QDate(), it determines the missing date from looking at all * the transactions which match the underlying filter, and returning the * date of the first or last transaction (as appropriate). * * @param _db The inclusive begin date of the date range * @param _de The inclusive end date of the date range */ void validDateRange(QDate& _db, QDate& _de); /** * This method turns on the account group filter and adds the * @p type to the list of allowed groups. * * Note that account group filtering is handled differently * than all the filters of the underlying class. This filter * is meant to be applied to individual splits of matched * transactions AFTER the underlying filter is used to find * the matching transactions. * * @param type the account group to add to the allowed groups list */ void addAccountGroup(MyMoneyAccount::accountTypeE type); /** * This method returns whether an account group filter has been set, * and if so, it returns all the account groups set in the filter. * * @param list list to append account groups into * @return return true if an account group filter has been set */ bool accountGroups(QList& list) const; /** * This method returns whether the specified account group * is allowed by the account groups filter. * * @param type group to append account groups into * @return return true if an account group filter has been set */ bool includesAccountGroup(MyMoneyAccount::accountTypeE type) const; /** * This method is used to test whether a specific account * passes the accountGroup test and either the Account or * Category test, depending on which sort of Account it is. * * The m_tax and m_investments properties are also considered. * * @param acc the account in question * @return true if account is in filter set, false otherwise */ bool includes(const MyMoneyAccount& acc) const; /** * This method writes this report to the DOM element @p e, * within the DOM document @p doc. * * @param e The element which should be populated with info from this report * @param doc The document which we can use to create new sub-elements * if needed * @param anonymous Whether the sensitive parts of the report should be * masked */ void write(QDomElement& e, QDomDocument *doc, bool anonymous = false) const; /** * This method reads a report from the DOM element @p e, and * populates this report with the results. * * @param e The element from which the report should be read * * @return bool True if a report was successfully loaded from the * element @p e. If false is returned, the contents of this report * object are undefined. */ bool read(const QDomElement& e); /** * This method creates a QDomElement for the @p document * under the parent node @p parent. (This version overwrites the * MMObject base class.) * * @param document reference to QDomDocument * @param parent reference to QDomElement parent node */ virtual void writeXML(QDomDocument& document, QDomElement& parent) const; /** * This method checks if a reference to the given object exists. It returns, * a @p true if the object is referencing the one requested by the * parameter @p id. If it does not, this method returns @p false. * * @param id id of the object to be checked for references * @retval true This object references object with id @p id. * @retval false This object does not reference the object with id @p id. */ virtual bool hasReferenceTo(const QString& id) const; /** * This method allows to modify the default lineWidth for graphs. * The default is 2. */ static void setLineWidth(int width); private: /** * The user-assigned name of the report */ QString m_name; /** * The user-assigned comment for the report, in case they want to make * additional notes for themselves about the report. */ QString m_comment; /** * Where to group this report amongst the others in the UI view. This * should be assigned by the UI system. */ QString m_group; /** * How much detail to show in the accounts */ DetailLevel::Type m_detailLevel; /** * Whether to convert all currencies to the base currency of the file (true). * If this is false, it's up to the report generator to decide how to handle * the currency. */ bool m_convertCurrency; /** * Whether this is one of the users' favorite reports */ bool m_favorite; /** * Whether this report should only include categories marked as "Tax"="Yes" */ bool m_tax; /** * Whether this report should only include investment accounts */ bool m_investments; /** * Whether this report should only include loan accounts * Applies only to querytable reports. Mutually exclusive with * m_investments. */ bool m_loans; /** * What sort of algorithm should be used to run the report */ Report::Type m_reportType; /** * What sort of values should show up on the ROWS of this report */ Row::Type m_rowType; /** * What sort of values should show up on the COLUMNS of this report, * in the case of a 'PivotTable' report. Really this is used more as a * QUANTITY of months or days. Whether it's months or days is determined * by m_columnsAreDays. */ Column::Type m_columnType; /** * Whether the base unit of columns of this report is days. Only applies to * 'PivotTable' reports. If false, then columns are months or multiples thereof. */ bool m_columnsAreDays; /** * What sort of values should show up on the COLUMNS of this report, * in the case of a 'QueryTable' report */ enum QueryColumns::Type m_queryColumns; /** * The plain-language description of what the date range should be locked * to. 'userDefined' means NO locking, in any other case, the report * will be adjusted to match the date lock. So if the date lock is * 'currentMonth', the start and end dates of the underlying filter will * be updated to whatever the current month is. This updating happens * automatically when the report is loaded, and should also be done * manually by calling updateDateFilter() before generating the report */ dateOptionE m_dateLock; /** * Which account groups should be included in the report. This filter * is applied to the individual splits AFTER a transaction has been * matched using the underlying filter. */ QList m_accountGroups; /** * Whether an account group filter has been set (see m_accountGroups) */ bool m_accountGroupFilter; /** * What format should be used to draw this report as a chart */ Chart::Type m_chartType; /** * Whether the value of individual data points should be drawn on the chart */ bool m_chartDataLabels; /** * Whether grid lines should be drawn on the chart */ bool m_chartGridLines; /** * Whether this report should be shown as a chart by default (otherwise it * should be shown as a textual report) */ bool m_chartByDefault; /** * Width of the chart lines */ uint m_chartLineWidth; /** * Whether to include scheduled transactions */ bool m_includeSchedules; /** * Whether to include transfers. Only applies to Income/Expense reports */ bool m_includeTransfers; /** * The id of the budget associated with this report. */ QString m_budgetId; /** * Whether this report should print the actual data to go along with * the budget. This is only valid if the report has a budget. */ bool m_includeBudgetActuals; /** * Whether this report should include all accounts and not only * accounts with transactions. */ bool m_includeUnusedAccounts; /** * Whether this report should include columns for row totals */ bool m_showRowTotals; /** * Whether this report should include forecast balance */ bool m_includeForecast; /** * Whether this report should include moving average */ bool m_includeMovingAverage; /** * The amount of days that spans each moving average */ int m_movingAverageDays; /** * Whether this report should include prices */ bool m_includePrice; /** * Whether this report should include moving average prices */ bool m_includeAveragePrice; /** * Make the actual and forecast lines display as one */ bool m_mixedTime; /** * This stores the column for the current date * This value is calculated dinamically and thus it is not saved in the file */ int m_currentDateColumn; /** * This member keeps the current setting for line graphs lineWidth. * @sa setLineWidth() */ static int m_lineWidth; /** * This option is for investments reports only which * show prices instead of balances as all other reports do. *

* Select this option to include prices for the given period (week, month, * quarter, ...) only. *

*

* If this option is off the last existing price is shown for a period, if * it is on, in a table the value is '0' shown and in a chart a linear * interpolation for the missing values will be performed. *
Example: *
There are prices for January and March, but there is no price for * February. *

    *
  • OFF: shows the price for February as the last price of * January *
  • ON: in a table the value is '0', in a chart a linear * interpolation for the February-price will be performed * (so it makes a kind of average-value using the January- and the * March-price in the chart) *
*

*/ bool m_skipZero; }; /** * Make it possible to hold @ref MyMoneyReport objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyReport) +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, const MyMoneyReport &a); #endif // MYMONEYREPORT_H diff --git a/kmymoney/mymoney/mymoneytransactionfilter.cpp b/kmymoney/mymoney/mymoneytransactionfilter.cpp index 4abc2fa3a..e8cdb4000 100644 --- a/kmymoney/mymoney/mymoneytransactionfilter.cpp +++ b/kmymoney/mymoney/mymoneytransactionfilter.cpp @@ -1,906 +1,920 @@ /*************************************************************************** mymoneytransactionfilter.cpp - description ------------------- begin : Fri Aug 22 2003 copyright : (C) 2003 by Thomas Baumgart 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. * * * ***************************************************************************/ #include "mymoneytransactionfilter.h" // ---------------------------------------------------------------------------- // QT Includes +#include "misc/debugindenter.h" + // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" MyMoneyTransactionFilter::MyMoneyTransactionFilter() { m_filterSet.allFilter = 0; m_reportAllSplits = true; m_considerCategory = true; m_invertText = false; } MyMoneyTransactionFilter::MyMoneyTransactionFilter(const QString& id) { m_filterSet.allFilter = 0; m_reportAllSplits = false; m_considerCategory = false; m_invertText = false; addAccount(id); // addCategory(id); } MyMoneyTransactionFilter::~MyMoneyTransactionFilter() { } void MyMoneyTransactionFilter::clear() { m_filterSet.allFilter = 0; m_invertText = false; m_accounts.clear(); m_categories.clear(); m_payees.clear(); m_tags.clear(); m_types.clear(); m_states.clear(); m_validity.clear(); m_matchingSplits.clear(); m_fromDate = QDate(); m_toDate = QDate(); } void MyMoneyTransactionFilter::clearAccountFilter() { m_filterSet.singleFilter.accountFilter = 0; m_accounts.clear(); } void MyMoneyTransactionFilter::setTextFilter(const QRegExp& text, bool invert) { m_filterSet.singleFilter.textFilter = 1; m_invertText = invert; m_text = text; } void MyMoneyTransactionFilter::addAccount(const QStringList& ids) { QStringList::ConstIterator it; m_filterSet.singleFilter.accountFilter = 1; for (it = ids.begin(); it != ids.end(); ++it) addAccount(*it); } void MyMoneyTransactionFilter::addAccount(const QString& id) { if (!m_accounts.isEmpty() && !id.isEmpty()) { if (m_accounts.find(id) != m_accounts.end()) return; } m_filterSet.singleFilter.accountFilter = 1; if (!id.isEmpty()) m_accounts.insert(id, ""); } void MyMoneyTransactionFilter::addCategory(const QStringList& ids) { QStringList::ConstIterator it; m_filterSet.singleFilter.categoryFilter = 1; for (it = ids.begin(); it != ids.end(); ++it) addCategory(*it); } void MyMoneyTransactionFilter::addCategory(const QString& id) { if (!m_categories.isEmpty() && !id.isEmpty()) { if (m_categories.end() != m_categories.find(id)) return; } m_filterSet.singleFilter.categoryFilter = 1; if (!id.isEmpty()) m_categories.insert(id, ""); } void MyMoneyTransactionFilter::setDateFilter(const QDate& from, const QDate& to) { m_filterSet.singleFilter.dateFilter = from.isValid() | to.isValid(); m_fromDate = from; m_toDate = to; } void MyMoneyTransactionFilter::setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to) { m_filterSet.singleFilter.amountFilter = 1; m_fromAmount = from.abs(); m_toAmount = to.abs(); // make sure that the user does not try to fool us ;-) if (from > to) { MyMoneyMoney tmp = m_fromAmount; m_fromAmount = m_toAmount; m_toAmount = tmp; } } void MyMoneyTransactionFilter::addPayee(const QString& id) { if (!m_payees.isEmpty() && !id.isEmpty()) { if (m_payees.find(id) != m_payees.end()) return; } m_filterSet.singleFilter.payeeFilter = 1; if (!id.isEmpty()) m_payees.insert(id, ""); } void MyMoneyTransactionFilter::addTag(const QString& id) { if (!m_tags.isEmpty() && !id.isEmpty()) { if (m_tags.find(id) != m_tags.end()) return; } m_filterSet.singleFilter.tagFilter = 1; if (!id.isEmpty()) m_tags.insert(id, ""); } void MyMoneyTransactionFilter::addType(const int type) { if (!m_types.isEmpty()) { if (m_types.find(type) != m_types.end()) return; } m_filterSet.singleFilter.typeFilter = 1; m_types.insert(type, ""); } void MyMoneyTransactionFilter::addState(const int state) { if (!m_states.isEmpty()) { if (m_states.find(state) != m_states.end()) return; } m_filterSet.singleFilter.stateFilter = 1; m_states.insert(state, ""); } void MyMoneyTransactionFilter::addValidity(const int type) { if (!m_validity.isEmpty()) { if (m_validity.find(type) != m_validity.end()) return; } m_filterSet.singleFilter.validityFilter = 1; m_validity.insert(type, ""); } void MyMoneyTransactionFilter::setNumberFilter(const QString& from, const QString& to) { m_filterSet.singleFilter.nrFilter = 1; m_fromNr = from; m_toNr = to; } void MyMoneyTransactionFilter::setReportAllSplits(const bool report) { m_reportAllSplits = report; } void MyMoneyTransactionFilter::setConsiderCategory(const bool check) { m_considerCategory = check; } const QList& MyMoneyTransactionFilter::matchingSplits() const { return m_matchingSplits; } bool MyMoneyTransactionFilter::matchText(const MyMoneySplit * const sp) const { // check if the text is contained in one of the fields // memo, value, number, payee, tag, account, date if (m_filterSet.singleFilter.textFilter) { MyMoneyFile* file = MyMoneyFile::instance(); const MyMoneyAccount& acc = file->account(sp->accountId()); const MyMoneySecurity& sec = file->security(acc.currencyId()); if (sp->memo().contains(m_text) || sp->shares().formatMoney(acc.fraction(sec)).contains(m_text) || sp->value().formatMoney(acc.fraction(sec)).contains(m_text) || sp->number().contains(m_text) || (m_text.pattern() == sp->transactionId())) return !m_invertText; if (acc.name().contains(m_text)) return !m_invertText; if (!sp->payeeId().isEmpty()) { const MyMoneyPayee& payee = file->payee(sp->payeeId()); if (payee.name().contains(m_text)) return !m_invertText; } if (!sp->tagIdList().isEmpty()) { QList::ConstIterator it_s; QList t = sp->tagIdList(); for (it_s = t.constBegin(); it_s != t.constEnd(); ++it_s) { const MyMoneyTag& tag = file->tag((*it_s)); if (tag.name().contains(m_text)) return !m_invertText; } } return m_invertText; } return true; } bool MyMoneyTransactionFilter::matchAmount(const MyMoneySplit * const sp) const { if (m_filterSet.singleFilter.amountFilter) { if (((sp->value().abs() < m_fromAmount) || sp->value().abs() > m_toAmount) && ((sp->shares().abs() < m_fromAmount) || sp->shares().abs() > m_toAmount)) return false; } return true; } bool MyMoneyTransactionFilter::match(const MyMoneySplit * const sp) const { return matchText(sp) && matchAmount(sp); } bool MyMoneyTransactionFilter::match(const MyMoneyTransaction& transaction) { MyMoneyFile* file = MyMoneyFile::instance(); m_matchingSplits.clear(); // qDebug("T: %s", transaction.id().data()); // if no filter is set, we can safely return a match // if we should report all splits, then we collect them if (!m_filterSet.allFilter) { if (m_reportAllSplits) { m_matchingSplits = transaction.splits(); } return true; } // perform checks on the MyMoneyTransaction object first // check the date range if (m_filterSet.singleFilter.dateFilter) { if (m_fromDate != QDate()) { if (transaction.postDate() < m_fromDate) return false; } if (m_toDate != QDate()) { if (transaction.postDate() > m_toDate) return false; } } // construct a local list of pointers to all splits and // remove the ones that do not match account and/or categories. QList matchingSplits; //QList matchingSplits; foreach (const MyMoneySplit& s, transaction.splits()) { matchingSplits.append(&s); } bool categoryMatched = !m_filterSet.singleFilter.categoryFilter; bool accountMatched = !m_filterSet.singleFilter.accountFilter; bool isTransfer = true; // check the transaction's validity if (m_filterSet.singleFilter.validityFilter) { if (m_validity.count() > 0) { if (m_validity.end() == m_validity.find(validTransaction(transaction))) return false; } } QMutableListIterator sp(matchingSplits); if (m_filterSet.singleFilter.accountFilter == 1 || m_filterSet.singleFilter.categoryFilter == 1) { for (sp.toFront(); sp.hasNext();) { bool removeSplit = true; const MyMoneySplit* & s = sp.next(); const MyMoneyAccount& acc = file->account(s->accountId()); if (m_considerCategory) { switch (acc.accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: isTransfer = false; // check if the split references one of the categories in the list if (m_filterSet.singleFilter.categoryFilter) { if (m_categories.count() > 0) { if (m_categories.end() != m_categories.find(s->accountId())) { categoryMatched = true; removeSplit = false; } } else { // we're looking for transactions with 'no' categories return false; } } break; default: // check if the split references one of the accounts in the list if (m_filterSet.singleFilter.accountFilter) { if (m_accounts.count() > 0) { if (m_accounts.end() != m_accounts.find(s->accountId())) { accountMatched = true; removeSplit = false; } } } else removeSplit = false; break; } } else { if (m_filterSet.singleFilter.accountFilter) { if (m_accounts.count() > 0) { if (m_accounts.end() != m_accounts.find(s->accountId())) { accountMatched = true; removeSplit = false; } } } else removeSplit = false; } if (removeSplit) { // qDebug(" S: %s", (*it).id().data()); sp.remove(); } } } // check if we're looking for transactions without assigned category if (!categoryMatched && transaction.splitCount() == 1 && m_categories.count() == 0) { categoryMatched = true; } // if there's no category filter and the category did not // match, then we still want to see this transaction if it's // a transfer if (!categoryMatched && !m_filterSet.singleFilter.categoryFilter) categoryMatched = isTransfer; if (matchingSplits.count() == 0 || !(accountMatched && categoryMatched)) return false; FilterSet filterSet = m_filterSet; filterSet.singleFilter.dateFilter = filterSet.singleFilter.accountFilter = filterSet.singleFilter.categoryFilter = 0; // check if we still have something to do if (filterSet.allFilter != 0) { for (sp.toFront(); sp.hasNext();) { bool removeSplit = true; const MyMoneySplit* & s = sp.next(); removeSplit = !(matchAmount(s) && matchText(s)); const MyMoneyAccount& acc = file->account(s->accountId()); // Determine if this account is a category or an account bool isCategory = false; switch (acc.accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: isCategory = true; default: break; } if (!isCategory && !removeSplit) { // check the payee list if (!removeSplit && m_filterSet.singleFilter.payeeFilter) { if (m_payees.count() > 0) { if (s->payeeId().isEmpty() || m_payees.end() == m_payees.find(s->payeeId())) removeSplit = true; } else if (!s->payeeId().isEmpty()) removeSplit = true; } // check the tag list if (!removeSplit && m_filterSet.singleFilter.tagFilter) { if (m_tags.count() > 0) { if (s->tagIdList().isEmpty()) removeSplit = true; else { bool found = false; for (int i = 0; i < s->tagIdList().size(); i++) { if (m_tags.end() != m_tags.find(s->tagIdList()[i])) { found = true; break; } } if (!found) { removeSplit = true; } } } else if (!s->tagIdList().isEmpty()) removeSplit = true; } // check the type list if (!removeSplit && m_filterSet.singleFilter.typeFilter) { if (m_types.count() > 0) { if (m_types.end() == m_types.find(splitType(transaction, *s))) removeSplit = true; } } // check the state list if (!removeSplit && m_filterSet.singleFilter.stateFilter) { if (m_states.count() > 0) { if (m_states.end() == m_states.find(splitState(*s))) removeSplit = true; } } if (!removeSplit && m_filterSet.singleFilter.nrFilter) { if (!m_fromNr.isEmpty()) { if (s->number() < m_fromNr) removeSplit = true; } if (!m_toNr.isEmpty()) { if (s->number() > m_toNr) removeSplit = true; } } } else if (m_filterSet.singleFilter.payeeFilter || m_filterSet.singleFilter.tagFilter || m_filterSet.singleFilter.typeFilter || m_filterSet.singleFilter.stateFilter || m_filterSet.singleFilter.nrFilter) removeSplit = true; if (removeSplit) { // qDebug(" S: %s", (*it).id().data()); sp.remove(); } } } if (m_reportAllSplits == false && matchingSplits.count() != 0) { m_matchingSplits.append(transaction.splits()[0]); } else { foreach (const MyMoneySplit* s, matchingSplits) { m_matchingSplits.append(*s); } } // all filters passed, I guess we have a match // qDebug(" C: %d", m_matchingSplits.count()); return matchingSplits.count() != 0; } int MyMoneyTransactionFilter::splitState(const MyMoneySplit& split) const { int rc = notReconciled; switch (split.reconcileFlag()) { default: case MyMoneySplit::NotReconciled: break;; case MyMoneySplit::Cleared: rc = cleared; break; case MyMoneySplit::Reconciled: rc = reconciled; break; case MyMoneySplit::Frozen: rc = frozen; break; } return rc; } int MyMoneyTransactionFilter::splitType(const MyMoneyTransaction& t, const MyMoneySplit& split) const { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount a, b; a = file->account(split.accountId()); if ((a.accountGroup() == MyMoneyAccount::Income || a.accountGroup() == MyMoneyAccount::Expense)) return allTypes; if (t.splitCount() == 2) { QString ida, idb; if (t.splits().size() > 0) ida = t.splits()[0].accountId(); if (t.splits().size() > 1) idb = t.splits()[1].accountId(); a = file->account(ida); b = file->account(idb); if ((a.accountGroup() != MyMoneyAccount::Expense && a.accountGroup() != MyMoneyAccount::Income) && (b.accountGroup() != MyMoneyAccount::Expense && b.accountGroup() != MyMoneyAccount::Income)) return transfers; } if (split.value().isPositive()) return deposits; return payments; } MyMoneyTransactionFilter::validityOptionE MyMoneyTransactionFilter::validTransaction(const MyMoneyTransaction& t) const { QList::ConstIterator it_s; MyMoneyMoney val; for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { val += (*it_s).value(); } return (val == MyMoneyMoney()) ? valid : invalid; } bool MyMoneyTransactionFilter::includesCategory(const QString& cat) const { return (! m_filterSet.singleFilter.categoryFilter) || m_categories.end() != m_categories.find(cat); } bool MyMoneyTransactionFilter::includesAccount(const QString& acc) const { return (! m_filterSet.singleFilter.accountFilter) || m_accounts.end() != m_accounts.find(acc); } bool MyMoneyTransactionFilter::includesPayee(const QString& pye) const { return (! m_filterSet.singleFilter.payeeFilter) || m_payees.end() != m_payees.find(pye); } bool MyMoneyTransactionFilter::includesTag(const QString& tag) const { return (! m_filterSet.singleFilter.tagFilter) || m_tags.end() != m_tags.find(tag); } bool MyMoneyTransactionFilter::dateFilter(QDate& from, QDate& to) const { from = m_fromDate; to = m_toDate; return m_filterSet.singleFilter.dateFilter == 1; } bool MyMoneyTransactionFilter::amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const { from = m_fromAmount; to = m_toAmount; return m_filterSet.singleFilter.amountFilter == 1; } bool MyMoneyTransactionFilter::numberFilter(QString& from, QString& to) const { from = m_fromNr; to = m_toNr; return m_filterSet.singleFilter.nrFilter == 1; } bool MyMoneyTransactionFilter::payees(QStringList& list) const { bool result = m_filterSet.singleFilter.payeeFilter; if (result) { QHashIterator it_payee(m_payees); while (it_payee.hasNext()) { it_payee.next(); list += it_payee.key(); } } return result; } bool MyMoneyTransactionFilter::tags(QStringList& list) const { bool result = m_filterSet.singleFilter.tagFilter; if (result) { QHashIterator it_tag(m_tags); while (it_tag.hasNext()) { it_tag.next(); list += it_tag.key(); } } return result; } bool MyMoneyTransactionFilter::accounts(QStringList& list) const { bool result = m_filterSet.singleFilter.accountFilter; if (result) { QHashIterator it_account(m_accounts); while (it_account.hasNext()) { it_account.next(); QString account = it_account.key(); list += account; } } return result; } bool MyMoneyTransactionFilter::categories(QStringList& list) const { bool result = m_filterSet.singleFilter.categoryFilter; if (result) { QHashIterator it_category(m_categories); while (it_category.hasNext()) { it_category.next(); list += it_category.key(); } } return result; } bool MyMoneyTransactionFilter::types(QList& list) const { bool result = m_filterSet.singleFilter.typeFilter; if (result) { QHashIterator it_type(m_types); while (it_type.hasNext()) { it_type.next(); list += it_type.key(); } } return result; } bool MyMoneyTransactionFilter::states(QList& list) const { bool result = m_filterSet.singleFilter.stateFilter; if (result) { QHashIterator it_state(m_states); while (it_state.hasNext()) { it_state.next(); list += it_state.key(); } } return result; } bool MyMoneyTransactionFilter::firstType(int&i) const { bool result = m_filterSet.singleFilter.typeFilter; if (result) { QHashIterator it_type(m_types); if (it_type.hasNext()) { it_type.next(); i = it_type.key(); } } return result; } bool MyMoneyTransactionFilter::firstState(int&i) const { bool result = m_filterSet.singleFilter.stateFilter; if (result) { QHashIterator it_state(m_states); if (it_state.hasNext()) { it_state.next(); i = it_state.key(); } } return result; } bool MyMoneyTransactionFilter::textFilter(QRegExp& exp) const { exp = m_text; return m_filterSet.singleFilter.textFilter == 1; } void MyMoneyTransactionFilter::setDateFilter(dateOptionE range) { QDate from, to; if (translateDateRange(range, from, to)) setDateFilter(from, to); } static int fiscalYearStartMonth = 1; static int fiscalYearStartDay = 1; void MyMoneyTransactionFilter::setFiscalYearStart(int firstMonth, int firstDay) { fiscalYearStartMonth = firstMonth; fiscalYearStartDay = firstDay; } bool MyMoneyTransactionFilter::translateDateRange(dateOptionE id, QDate& start, QDate& end) { bool rc = true; int yr = QDate::currentDate().year(); int mon = QDate::currentDate().month(); switch (id) { case MyMoneyTransactionFilter::allDates: start = QDate(); end = QDate(); break; case MyMoneyTransactionFilter::asOfToday: start = QDate(); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::currentMonth: start = QDate(yr, mon, 1); end = QDate(yr, mon, 1).addMonths(1).addDays(-1); break; case MyMoneyTransactionFilter::currentYear: start = QDate(yr, 1, 1); end = QDate(yr, 12, 31); break; case MyMoneyTransactionFilter::monthToDate: start = QDate(yr, mon, 1); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::yearToDate: start = QDate(yr, 1, 1); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::yearToMonth: start = QDate(yr, 1, 1); end = QDate(yr, mon, 1).addDays(-1); break; case MyMoneyTransactionFilter::lastMonth: start = QDate(yr, mon, 1).addMonths(-1); end = QDate(yr, mon, 1).addDays(-1); break; case MyMoneyTransactionFilter::lastYear: start = QDate(yr, 1, 1).addYears(-1); end = QDate(yr, 12, 31).addYears(-1); break; case MyMoneyTransactionFilter::last7Days: start = QDate::currentDate().addDays(-7); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last30Days: start = QDate::currentDate().addDays(-30); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last3Months: start = QDate::currentDate().addMonths(-3); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last6Months: start = QDate::currentDate().addMonths(-6); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::last11Months: start = QDate(yr, mon, 1).addMonths(-12); end = QDate(yr, mon, 1).addDays(-1); break; case MyMoneyTransactionFilter::last12Months: start = QDate::currentDate().addMonths(-12); end = QDate::currentDate(); break; case MyMoneyTransactionFilter::next7Days: start = QDate::currentDate(); end = QDate::currentDate().addDays(7); break; case MyMoneyTransactionFilter::next30Days: start = QDate::currentDate(); end = QDate::currentDate().addDays(30); break; case MyMoneyTransactionFilter::next3Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(3); break; case MyMoneyTransactionFilter::next6Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(6); break; case MyMoneyTransactionFilter::next12Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(12); break; case MyMoneyTransactionFilter::next18Months: start = QDate::currentDate(); end = QDate::currentDate().addMonths(18); break; case MyMoneyTransactionFilter::userDefined: start = QDate(); end = QDate(); break; case MyMoneyTransactionFilter::last3ToNext3Months: start = QDate::currentDate().addMonths(-3); end = QDate::currentDate().addMonths(3); break; case MyMoneyTransactionFilter::currentQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1); end = start.addMonths(3).addDays(-1); break; case MyMoneyTransactionFilter::lastQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(-3); end = start.addMonths(3).addDays(-1); break; case MyMoneyTransactionFilter::nextQuarter: start = QDate(yr, mon - ((mon - 1) % 3), 1).addMonths(3); end = start.addMonths(3).addDays(-1); break; case MyMoneyTransactionFilter::currentFiscalYear: start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay); if (QDate::currentDate() < start) start = start.addYears(-1); end = start.addYears(1).addDays(-1); break; case MyMoneyTransactionFilter::lastFiscalYear: start = QDate(QDate::currentDate().year(), fiscalYearStartMonth, fiscalYearStartDay); if (QDate::currentDate() < start) start = start.addYears(-1); start = start.addYears(-1); end = start.addYears(1).addDays(-1); break; case MyMoneyTransactionFilter::today: start = QDate::currentDate(); end = QDate::currentDate(); break; default: qWarning("Unknown date identifier %d in MyMoneyTransactionFilter::translateDateRange()", id); rc = false; break; } return rc; } void MyMoneyTransactionFilter::removeReference(const QString& id) { if (m_accounts.end() != m_accounts.find(id)) { qDebug("%s", qPrintable(QString("Remove account '%1' from report").arg(id))); m_accounts.take(id); } else if (m_categories.end() != m_categories.find(id)) { qDebug("%s", qPrintable(QString("Remove category '%1' from report").arg(id))); m_categories.remove(id); } else if (m_payees.end() != m_payees.find(id)) { qDebug("%s", qPrintable(QString("Remove payee '%1' from report").arg(id))); m_payees.remove(id); } else if (m_tags.end() != m_tags.find(id)) { qDebug("%s", qPrintable(QString("Remove tag '%1' from report").arg(id))); m_tags.remove(id); } } + +QDebug operator<<(QDebug dbg, const MyMoneyTransactionFilter &a) +{ + QStringList accounts; + a.accounts(accounts); + QStringList payees; + a.accounts(payees); + return DebugIndenter(dbg, typeid(a).name()) + << "accounts" << accounts + << "payees" << payees + ; +} diff --git a/kmymoney/mymoney/mymoneytransactionfilter.h b/kmymoney/mymoney/mymoneytransactionfilter.h index e95b92d4f..d56233439 100644 --- a/kmymoney/mymoney/mymoneytransactionfilter.h +++ b/kmymoney/mymoney/mymoneytransactionfilter.h @@ -1,618 +1,619 @@ /*************************************************************************** mymoneytransactionfilter.h - description ------------------- begin : Fri Aug 22 2003 copyright : (C) 2000-2003 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 MYMONEYTRANSACTIONFILTER_H #define MYMONEYTRANSACTIONFILTER_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneytransaction.h" #include "kmm_mymoney_export.h" /** * @author Thomas Baumgart */ class KMM_MYMONEY_EXPORT MyMoneyTransactionFilter { public: // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum typeOptionE { allTypes = 0, payments, deposits, transfers, // insert new constants above of this line typeOptionCount }; // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum stateOptionE { allStates = 0, notReconciled, cleared, reconciled, frozen, // insert new constants above of this line stateOptionCount }; // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum validityOptionE { anyValidity = 0, valid, invalid, // insert new constants above of this line validityOptionCount }; // Make sure to keep the following enum valus in sync with the values // used by the GUI (for KMyMoney in kfindtransactiondlgdecl.ui) enum dateOptionE { allDates = 0, asOfToday, currentMonth, currentYear, monthToDate, yearToDate, yearToMonth, lastMonth, lastYear, last7Days, last30Days, last3Months, last6Months, last12Months, next7Days, next30Days, next3Months, next6Months, next12Months, userDefined, last3ToNext3Months, last11Months, currentQuarter, lastQuarter, nextQuarter, currentFiscalYear, lastFiscalYear, today, next18Months, // insert new constants above of this line dateOptionCount }; typedef union { unsigned allFilter; struct { unsigned textFilter : 1; unsigned accountFilter : 1; unsigned payeeFilter : 1; unsigned tagFilter : 1; unsigned categoryFilter : 1; unsigned nrFilter : 1; unsigned dateFilter : 1; unsigned amountFilter : 1; unsigned typeFilter : 1; unsigned stateFilter : 1; unsigned validityFilter : 1; } singleFilter; } FilterSet; /** * This is the standard constructor for a transaction filter. * It creates the object and calls setReportAllSplits() to * report all matching splits as separate entries. Use * setReportAllSplits() to override this behaviour. */ MyMoneyTransactionFilter(); /** * This is a convenience constructor to allow construction of * a simple account filter. It is basically the same as the * following: * * @code * : * MyMoneyTransactionFilter filter; * filter.setReportAllSplits(false); * filter.addAccount(id); * : * @endcode * * @param id reference to account id */ MyMoneyTransactionFilter(const QString& id); ~MyMoneyTransactionFilter(); /** * This method is used to clear the filter. All settings will be * removed. */ void clear(); /** * This method is used to clear the accounts filter only. */ void clearAccountFilter(); /** * This method is used to set the regular expression filter to the value specified * as parameter @p exp. The following text based fields are searched: * * - Memo * - Payee * - Tag * - Category * - Shares / Value * - Number * * @param exp The regular expression that must be found in a transaction * before it is included in the result set. * @param invert If true, value must not be contained in any of the above mentioned fields * */ void setTextFilter(const QRegExp& exp, bool invert = false); /** * This method will add the account with id @p id to the list of matching accounts. * If the list is empty, any transaction will match. * * @param id internal ID of the account */ void addAccount(const QString& id); /** * This is a convenience method and behaves exactly like the above * method but for a list of id's. */ void addAccount(const QStringList& ids); /** * This method will add the category with id @p id to the list of matching categories. * If the list is empty, only transaction with a single asset/liability account will match. * * @param id internal ID of the account */ void addCategory(const QString& id); /** * This is a convenience method and behaves exactly like the above * method but for a list of id's. */ void addCategory(const QStringList& ids); /** * This method sets the date filter to match only transactions with posting dates in * the date range specified by @p from and @p to. If @p from equal QDate() * all transactions with dates prior to @p to match. If @p to equals QDate() * all transactions with posting dates past @p from match. If @p from and @p to * are equal QDate() the filter is not activated and all transactions match. * * @param from from date * @param to to date */ void setDateFilter(const QDate& from, const QDate& to); void setDateFilter(dateOptionE range); /** * This method sets the amount filter to match only transactions with * an amount in the range specified by @p from and @p to. * If a specific amount should be searched, @p from and @p to should be * the same value. * * @param from smallest value to match * @param to largest value to match */ void setAmountFilter(const MyMoneyMoney& from, const MyMoneyMoney& to); /** * This method will add the payee with id @p id to the list of matching payees. * If the list is empty, any transaction will match. * * @param id internal id of the payee */ void addPayee(const QString& id); /** * This method will add the tag with id @ta id to the list of matching tags. * If the list is empty, any transaction will match. * * @param id internal id of the tag */ void addTag(const QString& id); /** */ void addType(const int type); /** */ void addValidity(const int type); /** */ void addState(const int state); /** * This method sets the number filter to match only transactions with * a number in the range specified by @p from and @p to. * If a specific number should be searched, @p from and @p to should be * the same value. * * @param from smallest value to match * @param to largest value to match * * @note @p from and @p to can contain alphanumeric text */ void setNumberFilter(const QString& from, const QString& to); /** * This method is used to check a specific transaction against the filter. * The transaction will match the whole filter, if all specified filters * match. If the filter is cleared using the clear() method, any transaciton * matches. * * @param transaction A transaction * * @retval true The transaction matches the filter set * @retval false The transaction does not match at least one of * the filters in the filter set */ bool match(const MyMoneyTransaction& transaction); /** * This method is used to check a specific split against the * text filter. The split will match if all specified and * checked filters match. If the filter is cleared using the clear() * method, any split matches. * * @param sp pointer to the split to be checked * * @retval true The split matches the filter set * @retval false The split does not match at least one of * the filters in the filter set */ bool matchText(const MyMoneySplit * const sp) const; /** * This method is used to check a specific split against the * amount filter. The split will match if all specified and * checked filters match. If the filter is cleared using the clear() * method, any split matches. * * @param sp pointer to the split to be checked * * @retval true The split matches the filter set * @retval false The split does not match at least one of * the filters in the filter set */ bool matchAmount(const MyMoneySplit * const sp) const; /** * Convenience method which actually returns matchText(sp) && matchAmount(sp). */ bool match(const MyMoneySplit * const sp) const; /** * This method is used to switch the amount of splits reported * by matchingSplits(). If the argument @p report is @p true (the default * if no argument specified) then matchingSplits() will return all * matching splits of the transaction. If @p report is set to @p false, * then only the very first matching split will be returned by * matchingSplits(). * * @param report controls the behaviour of matchingsSplits() as explained above. */ void setReportAllSplits(const bool report = true); void setConsiderCategory(const bool check = true); /** * This method returns a list of the matching splits for the filter. * If m_reportAllSplits is set to false, then only the very first * split will be returned. Use setReportAllSplits() to change the * behaviour. * * @return reference list of MyMoneySplit objects containing the * matching splits. If multiple splits match, only the first * one will be returned. * * @note an empty list will be returned, if the filter only required * to check the data contained in the MyMoneyTransaction * object (e.g. posting-date, state, etc.). * * @note The constructors set m_reportAllSplits differently. Please * see the documentation of the constructors MyMoneyTransactionFilter() * and MyMoneyTransactionFilter(const QString&) for details. */ const QList& matchingSplits() const; /** * This method returns the from date set in the filter. If * no value has been set up for this filter, then QDate() is * returned. * * @return returns m_fromDate */ const QDate fromDate() const { return m_fromDate; }; /** * This method returns the to date set in the filter. If * no value has been set up for this filter, then QDate() is * returned. * * @return returns m_toDate */ const QDate toDate() const { return m_toDate; }; /** * This method is used to return information about the * presence of a specific category in the category filter. * The category in question is included in the filter set, * if it has been set or no category filter is set. * * @param cat id of category in question * @return true if category is in filter set, false otherwise */ bool includesCategory(const QString& cat) const; /** * This method is used to return information about the * presence of a specific account in the account filter. * The account in question is included in the filter set, * if it has been set or no account filter is set. * * @param acc id of account in question * @return true if account is in filter set, false otherwise */ bool includesAccount(const QString& acc) const; /** * This method is used to return information about the * presence of a specific payee in the account filter. * The payee in question is included in the filter set, * if it has been set or no account filter is set. * * @param pye id of payee in question * @return true if payee is in filter set, false otherwise */ bool includesPayee(const QString& pye) const; /** * This method is used to return information about the * presence of a specific tag in the account filter. * The tag in question is included in the filter set, * if it has been set or no account filter is set. * * @param tag id of tag in question * @return true if tag is in filter set, false otherwise */ bool includesTag(const QString& tag) const; /** * This method is used to return information about the * presence of a date filter. * * @param from result value for the beginning of the date range * @param to result value for the end of the date range * @return true if a date filter is set */ bool dateFilter(QDate& from, QDate& to) const; /** * This method is used to return information about the * presence of an amount filter. * * @param from result value for the low end of the amount range * @param to result value for the high end of the amount range * @return true if an amount filter is set */ bool amountFilter(MyMoneyMoney& from, MyMoneyMoney& to) const; /** * This method is used to return information about the * presence of an number filter. * * @param from result value for the low end of the number range * @param to result value for the high end of the number range * @return true if a number filter is set */ bool numberFilter(QString& from, QString& to) const; /** * This method returns whether a payee filter has been set, * and if so, it returns all the payees set in the filter. * * @param list list to append payees into * @return return true if a payee filter has been set */ bool payees(QStringList& list) const; /** * This method returns whether a tag filter has been set, * and if so, it returns all the tags set in the filter. * * @param list list to append tags into * @return return true if a tag filter has been set */ bool tags(QStringList& list) const; /** * This method returns whether an account filter has been set, * and if so, it returns all the accounts set in the filter. * * @param list list to append accounts into * @return return true if an account filter has been set */ bool accounts(QStringList& list) const; /** * This method returns whether a category filter has been set, * and if so, it returns all the categories set in the filter. * * @param list list to append categories into * @return return true if a category filter has been set */ bool categories(QStringList& list) const; /** * This method returns whether a type filter has been set, * and if so, it returns the first type in the filter. * * @param i int to replace with first type filter, untouched otherwise * @return return true if a type filter has been set */ bool firstType(int& i) const; bool types(QList& list) const; /** * This method returns whether a state filter has been set, * and if so, it returns the first state in the filter. * * @param i reference to int to replace with first state filter, untouched otherwise * @return return true if a state filter has been set */ bool firstState(int& i) const; bool states(QList& list) const; /** * This method returns whether a text filter has been set, * and if so, it returns the text filter. * * @param text regexp to replace with text filter, or blank if none set * @return return true if a text filter has been set */ bool textFilter(QRegExp& text) const; /** * This method returns whether the text filter should return * that DO NOT contain the text */ bool isInvertingText() const { return m_invertText; }; /** * This method translates a plain-language date range into QDate * start & end * * @param range Plain-language range of dates, e.g. 'CurrentYear' * @param start QDate will be set to corresponding to the first date in @p range * @param end QDate will be set to corresponding to the last date in @p range * @return return true if a range was successfully set, or false if @p range was invalid */ static bool translateDateRange(dateOptionE range, QDate& start, QDate& end); static void setFiscalYearStart(int firstMonth, int firstDay); FilterSet filterSet() const { return m_filterSet; }; /** * This member removes all references to object identified by @p id. Used * to remove objects which are about to be removed from the engine. */ void removeReference(const QString& id); private: /** * This is a conversion tool from MyMoneySplit::reconcileFlagE * to MyMoneyTransactionFilter::stateE types * * @param split reference to split in question * * @return converted reconcile flag of the split passed as parameter */ int splitState(const MyMoneySplit& split) const; /** * This is a conversion tool from MyMoneySplit::action * to MyMoneyTransactionFilter::typeE types * * @param t reference to transaction * @param split reference to split in question * * @return converted action of the split passed as parameter */ int splitType(const MyMoneyTransaction& t, const MyMoneySplit& split) const; /** * This method checks if a transaction is valid or not. A transaction * is considered valid, if the sum of all splits is zero, invalid otherwise. * * @param transaction reference to transaction to be checked * @retval valid transaction is valid * @retval invalid transaction is invalid */ validityOptionE validTransaction(const MyMoneyTransaction& transaction) const; protected: FilterSet m_filterSet; bool m_reportAllSplits; bool m_considerCategory; QRegExp m_text; bool m_invertText; QHash m_accounts; QHash m_payees; QHash m_tags; QHash m_categories; QHash m_states; QHash m_types; QHash m_validity; QString m_fromNr, m_toNr; QDate m_fromDate, m_toDate; MyMoneyMoney m_fromAmount, m_toAmount; QList m_matchingSplits; }; /** * Make it possible to hold @ref MyMoneyTransactionFilter objects inside @ref QVariant objects. */ Q_DECLARE_METATYPE(MyMoneyTransactionFilter) +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, const MyMoneyTransactionFilter &a); #endif diff --git a/kmymoney/reports/CMakeLists.txt b/kmymoney/reports/CMakeLists.txt index 44369798a..17727a69b 100644 --- a/kmymoney/reports/CMakeLists.txt +++ b/kmymoney/reports/CMakeLists.txt @@ -1,48 +1,48 @@ set (libreports_a_SOURCES kreportchartview.cpp reportaccount.cpp listtable.cpp objectinfotable.cpp pivotgrid.cpp pivottable.cpp querytable.cpp reporttable.cpp kbalanceaxis.cpp ) kde4_add_library(reports STATIC ${libreports_a_SOURCES}) target_link_libraries(reports kmm_kdchart) add_dependencies(reports kmm_settings) ############## tests #################### if( KDE4_BUILD_TESTS ) # Pivot Grid set( pivotgridtest_SRCS pivotgridtest.cpp reportstestcommon.cpp ) kde4_add_unit_test( pivotgridtest TESTNAME kmymoney-pivotgridtest ${pivotgridtest_SRCS} ) - add_dependencies( pivotgridtest reports kmymoney_base kmymoney_common kmm_storage ) + add_dependencies( pivotgridtest reports kmymoney_base kmymoney_common kmm_storage kmm_mymoney) - target_link_libraries( pivotgridtest reports kmymoney_base kmymoney_common kmm_storage ${KDE4_KDEUI_LIBS} ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} ) + target_link_libraries( pivotgridtest kmm_mymoney reports kmymoney_base kmymoney_common kmm_storage ${KDE4_KDEUI_LIBS} ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} ) # Pivot Table set( pivottabletest_SRCS pivottabletest.cpp reportstestcommon.cpp ../mymoney/mymoneytestutils.cpp ) kde4_add_unit_test( pivottabletest TESTNAME kmymoney-pivottabletest ${pivottabletest_SRCS} ) add_dependencies( pivottabletest dialogs widgets reports settings kmymoney_base kmymoney_common kmm_kdchart kmm_storage kmm_settings ) target_link_libraries( pivottabletest reports settings kmymoney_base kmymoney_common dialogs widgets kmm_kdchart kmm_storage kmm_settings ${KDE4_KDEUI_LIBS} ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} ) # Query Table set( querytabletest_SRCS querytabletest.cpp reportstestcommon.cpp ../mymoney/mymoneytestutils.cpp ) kde4_add_unit_test( querytabletest TESTNAME kmymoney-querytabletest ${querytabletest_SRCS} ) add_dependencies( querytabletest dialogs widgets reports settings kmymoney_base kmymoney_common kmm_storage kmm_settings ) target_link_libraries( querytabletest reports kmymoney_base kmymoney_common dialogs widgets kmm_storage kmm_settings ${KDE4_KDEUI_LIBS} ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTXML_LIBRARY} ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} ) kde4_add_executable( reportcharttest reportcharttest.cpp ) target_link_libraries( reportcharttest reports kmm_kdchart ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY} ) endif( KDE4_BUILD_TESTS ) diff --git a/kmymoney/reports/pivotgrid.cpp b/kmymoney/reports/pivotgrid.cpp index 7b82476b3..c83220288 100644 --- a/kmymoney/reports/pivotgrid.cpp +++ b/kmymoney/reports/pivotgrid.cpp @@ -1,224 +1,202 @@ /*************************************************************************** pivotgrid.cpp ------------------- begin : Mon May 17 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. * * * ***************************************************************************/ #include // ---------------------------------------------------------------------------- // QT Includes -#include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes +#include + namespace reports { const unsigned PivotOuterGroup::m_kDefaultSortOrder = 100; PivotCell::PivotCell(const MyMoneyMoney& value) : MyMoneyMoney(value), m_stockSplit(MyMoneyMoney::ONE), m_cellUsed(!value.isZero()) { } PivotCell::~PivotCell() { } PivotCell PivotCell::operator += (const PivotCell& right) { const MyMoneyMoney& r = static_cast(right); *this += r; m_postSplit = m_postSplit * right.m_stockSplit; m_stockSplit = m_stockSplit * right.m_stockSplit; m_postSplit += right.m_postSplit; m_cellUsed |= right.m_cellUsed; return *this; } PivotCell PivotCell::operator += (const MyMoneyMoney& value) { m_cellUsed |= !value.isZero(); if (m_stockSplit != MyMoneyMoney::ONE) m_postSplit += value; else MyMoneyMoney::operator += (value); return *this; } PivotCell PivotCell::stockSplit(const MyMoneyMoney& factor) { PivotCell s; s.m_stockSplit = factor; return s; } const QString PivotCell::formatMoney(int fraction, bool showThousandSeparator) const { return formatMoney("", MyMoneyMoney::denomToPrec(fraction), showThousandSeparator); } const QString PivotCell::formatMoney(const QString& currency, const int prec, bool showThousandSeparator) const { // construct the result MyMoneyMoney res = (*this * m_stockSplit) + m_postSplit; return res.formatMoney(currency, prec, showThousandSeparator); } MyMoneyMoney PivotCell::calculateRunningSum(const MyMoneyMoney& runningSum) { MyMoneyMoney::operator += (runningSum); MyMoneyMoney::operator = ((*this * m_stockSplit) + m_postSplit); m_postSplit = MyMoneyMoney(); m_stockSplit = MyMoneyMoney::ONE; return *this; } MyMoneyMoney PivotCell::cellBalance(const MyMoneyMoney& _balance) { MyMoneyMoney balance(_balance); balance += *this; balance = (balance * m_stockSplit) + m_postSplit; return balance; } PivotGridRowSet::PivotGridRowSet(unsigned _numcolumns) { insert(eActual, PivotGridRow(_numcolumns)); insert(eBudget, PivotGridRow(_numcolumns)); insert(eBudgetDiff, PivotGridRow(_numcolumns)); insert(eForecast, PivotGridRow(_numcolumns)); insert(eAverage, PivotGridRow(_numcolumns)); insert(ePrice, PivotGridRow(_numcolumns)); } PivotGridRowSet PivotGrid::rowSet(QString id) { //go through the data and get the row that matches the id PivotGrid::iterator it_outergroup = begin(); while (it_outergroup != 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 (it_row.key().id() == id) return it_row.value(); ++it_row; } ++it_innergroup; } ++it_outergroup; } return PivotGridRowSet(); } } // namespace QDebug operator<<(QDebug dbg, reports::PivotGrid &a) { - dbg.nospace() << "reports::PivotGrid(" - << "QMap("; + DebugIndenter d(dbg, typeid(a).name()); + DebugIndenter e(dbg, "QMap"); foreach(const QString &key, a.keys()) { - dbg.nospace() << key << ": " << a[key] << ","; + dbg.nospace() << key << ":" << a[key]; } - dbg << ")" - << ")"; return dbg; } QDebug operator<<(QDebug dbg, const reports::PivotCell &a) { - dbg.space() << "reports::PivotCell(" - //;dbg = operator<<(dbg, static_cast(a)) - << "MyMoneyMoney(" - << "isAutoCalc" << a.isAutoCalc() - << "isNegative" << a.isNegative() - << "isPositive" << a.isPositive() - << "isZero" << a.isZero() - << "value" << a.toString() - << ")" - << ")"; - return dbg; + return DebugIndenter(dbg, typeid(a).name()) + << static_cast(a) + << "isUsed" << a.isUsed(); } QDebug operator<<(QDebug dbg, const reports::ReportAccount &a) { - dbg.space() << "reports::ReportAccount(" - //;dbg = operator<<(dbg, static_cast(a)) - << "id" << a.id() - << "accountType" << MyMoneyAccount::accountTypeToString(a.accountType()) - << ")"; - return dbg; + return DebugIndenter(dbg, typeid(a).name()) + << static_cast(a); } QDebug operator<<(QDebug dbg, const QMap &a) { - dbg.space() << "reports::PivotOuterGroup(" - << "QMap("; + DebugIndenter d(dbg, typeid(a).name()); + DebugIndenter e(dbg, "QMap"); foreach(const QString &key, a.keys()) { - dbg << key << ":" << a[key] << ","; + dbg.nospace() << key << ":" << a[key]; } - dbg << ")" - << ")"; return dbg; } QDebug operator<<(QDebug dbg, const QMap &a) { - dbg.space() << "reports::PivotInnerGroup(" - << "QMap("; + DebugIndenter d(dbg, typeid(a).name()); + DebugIndenter e(dbg, "QMap"); foreach(const QString &key, a.keys()) { - dbg << key << ":" << a[key] << ","; + dbg.nospace() << key << ":" << a[key]; } - dbg << ")" - << ")"; return dbg; } QDebug operator<<(QDebug dbg, const QMap &a) { - dbg.space() << "reports::PivotGridRowSet(" - << "QMap("; + DebugIndenter d(dbg, typeid(a).name()); + DebugIndenter e(dbg, "QMap"); foreach(const reports::ReportAccount &key, a.keys()) { - dbg << key << ":" << a[key] << ","; + dbg.nospace() << key << ":" << a[key]; } - dbg << ")" - << ")"; return dbg; } QDebug operator<<(QDebug dbg, const QMap &a) { - dbg.space() << "reports::PivotGridRow(" - << "QMap("; + DebugIndenter d(dbg, typeid(a).name()); + DebugIndenter e(dbg, "QMap"); foreach(const reports::ERowType &key, a.keys()) { - dbg << key << ":" << a[key] << ","; + dbg.nospace() << key << ":" << a[key]; } - dbg << ")" - << ")"; return dbg; } - diff --git a/kmymoney/reports/pivotgrid.h b/kmymoney/reports/pivotgrid.h index e2376041c..2a2c95b93 100644 --- a/kmymoney/reports/pivotgrid.h +++ b/kmymoney/reports/pivotgrid.h @@ -1,161 +1,162 @@ /*************************************************************************** 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 +#include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "reportaccount.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); 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) { for (uint i = 0; i < _numcolumns; i++) append(PivotCell()); } MyMoneyMoney m_total; }; class PivotGridRowSet: public QMap { public: PivotGridRowSet(unsigned _numcolumns = 0); }; class PivotInnerGroup: public QMap { public: 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; }; } QDebug operator<<(QDebug dbg, reports::PivotGrid &a); QDebug operator<<(QDebug dbg, const reports::PivotCell &a); QDebug operator<<(QDebug dbg, const reports::ReportAccount &a); QDebug operator<<(QDebug dbg, const QMap &a); QDebug operator<<(QDebug dbg, const QMap &a); QDebug operator<<(QDebug dbg, const QMap &a); QDebug operator<<(QDebug dbg, const QMap &a); #endif // PIVOTGRID_H diff --git a/kmymoney/reports/pivottable.cpp b/kmymoney/reports/pivottable.cpp index 5fa7c8b81..49d8bf6bd 100644 --- a/kmymoney/reports/pivottable.cpp +++ b/kmymoney/reports/pivottable.cpp @@ -1,2303 +1,2301 @@ /*************************************************************************** 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 #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "pivotgrid.h" #include "reportdebug.h" #include "kreportchartview.h" #include "kmymoneyglobalsettings.h" #include "kmymoneyutils.h" #include "mymoneyforecast.h" #include +#include namespace reports { using KDChart::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& _config_f): ReportTable(), m_runningSumsCalculated(false), m_config_f(_config_f) { 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_f.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_f.isRunningSum()) m_config_f.setShowingRowTotals(false); // if this is a months-based report if (! m_config_f.isColumnsAreDays()) { // strip out the 'days' component of the begin and end dates. // we're only using these variables to contain year and month. m_beginDate = QDate(m_beginDate.year(), m_beginDate.month(), 1); m_endDate = QDate(m_endDate.year(), m_endDate.month(), 1); } m_numColumns = columnValue(m_endDate) - columnValue(m_beginDate) + 2; //Load what types of row the report is going to show loadRowTypeList(); // // Initialize outer groups of the grid // if (m_config_f.rowType() == MyMoneyReport::Row::AssetLiability) { m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Asset), PivotOuterGroup(m_numColumns)); m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Liability), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder, true /* inverted */)); } else { m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Income), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 2)); m_grid.insert(KMyMoneyUtils::accountTypeToString(MyMoneyAccount::Expense), PivotOuterGroup(m_numColumns, PivotOuterGroup::m_kDefaultSortOrder - 1, true /* inverted */)); // // Create rows for income/expense reports with all accounts included // if (m_config_f.isIncludingUnusedAccounts()) createAccountRows(); } // // Initialize grid totals // m_grid.m_total = PivotGridRowSet(m_numColumns); // // Get opening balances // (for running sum reports only) // if (m_config_f.isRunningSum()) calculateOpeningBalances(); // // Calculate budget mapping // (for budget reports only) // if (m_config_f.hasBudget()) calculateBudgetMapping(); // // Populate all transactions into the row/column pivot grid // QList transactions; m_config_f.setReportAllSplits(false); m_config_f.setConsiderCategory(true); try { transactions = file->transactionList(m_config_f); } 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_f.isIncludingSchedules()) { // Create a custom version of the report filter, excluding date // We'll use this to compare the transaction against MyMoneyTransactionFilter schedulefilter(m_config_f); schedulefilter.setDateFilter(QDate(), QDate()); // Get the real dates from the config filter QDate configbegin, configend; m_config_f.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_f.rowType() == MyMoneyReport::Row::ExpenseIncome) && (m_config_f.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) - 1; 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()); MyMoneyAccount::accountTypeE type = splitAccount.accountGroup(); QString outergroup = KMyMoneyUtils::accountTypeToString(type); //if the account is included in the report, calculate the balance from the cells if (m_config_f.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_f.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(); // 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_f.includes(splitAccount) && m_config_f.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) MyMoneyAccount::accountTypeE type = splitAccount.accountGroup(); QString outergroup = KMyMoneyUtils::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 == MyMoneyAccount::Asset || type == MyMoneyAccount::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_f.isIncludingForecast()) calculateForecast(); // //Insert Price data // if (m_config_f.isIncludingPrice()) fillBasePriceUnit(ePrice); // //Insert Average Price data // if (m_config_f.isIncludingAveragePrice()) { fillBasePriceUnit(eActual); calculateMovingAverage(); } // // Collapse columns to match column type // if (m_config_f.columnPitch() > 1) collapseColumns(); // // Calculate the running sums // (for running sum reports only) // if (m_config_f.isRunningSum()) calculateRunningSums(); // // Calculate Moving Average // if (m_config_f.isIncludingMovingAverage()) calculateMovingAverage(); // // Calculate Budget Difference // if (m_config_f.isIncludingBudgetActuals()) calculateBudgetDiff(); // // Convert all values to the deep currency // convertToDeepCurrency(); // // Convert all values to the base currency // if (m_config_f.isConvertCurrency()) convertToBaseCurrency(); // // Determine column headings // calculateColumnHeadings(); // // Calculate row and column totals // calculateTotals(); // // If using mixed time, calculate column for current date // m_config_f.setCurrentDateColumn(currentDateColumn()); } void PivotTable::collapseColumns() { DEBUG_ENTER(Q_FUNC_INFO); int columnpitch = m_config_f.columnPitch(); if (columnpitch != 1) { int sourcemonth = (m_config_f.isColumnsAreDays()) // use the user's locale to determine the week's start ? (m_beginDate.dayOfWeek() + 8 - KGlobal::locale()->weekStartDay()) % 7 : m_beginDate.month(); int sourcecolumn = 1; int destcolumn = 1; 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 m_columnHeadings.append("Opening"); int columnpitch = m_config_f.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_f.isColumnsAreDays()) { if (columnpitch == 1) { QDate columnDate = m_beginDate; int column = 1; while (column++ < m_numColumns) { QString heading = KGlobal::locale()->calendar()->monthName(columnDate.month(), columnDate.year(), KCalendarSystem::ShortName) + ' ' + 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 - KGlobal::locale()->weekStartDay()) % 7; while (day <= m_endDate) { if (((dow % columnpitch) == 0) || (day == m_endDate)) { m_columnHeadings.append(QString("%1 %2 - %3 %4") .arg(KGlobal::locale()->calendar()->monthName(prv.month(), prv.year(), KCalendarSystem::ShortName)) .arg(prv.day()) .arg(KGlobal::locale()->calendar()->monthName(day.month(), day.year(), KCalendarSystem::ShortName)) .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 = 1; 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 = 1; while (column++ < m_numColumns) { QString heading = KGlobal::locale()->calendar()->monthName(1 + segment * columnpitch, 2000, KCalendarSystem::ShortName); if (columnpitch != 1) heading += '-' + KGlobal::locale()->calendar()->monthName((1 + segment) * columnpitch, 2000, KCalendarSystem::ShortName); 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; // only include this item if its account group is included in this report // and if the report includes this account if (m_config_f.includes(*it_account)) { DEBUG_OUTPUT(QString("Includes account %1").arg(account.name())); // the row group is the account class (major account type) QString outergroup = KMyMoneyUtils::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_f.fromDate(); QDate to = m_config_f.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; // only include this item if its account group is included in this report // and if the report includes this account if (m_config_f.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 = KMyMoneyUtils::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 = 1; 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 = 1; 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_f.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; } // 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 = 1; 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_f.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_f.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_f.setBudget(budget.id(), m_config_f.isIncludingBudgetActuals()); } // Dump the budget //kDebug(2) << "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_f.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; // kDebug(2) << 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_f.isIncludingBudgetActuals()) { m_budgetMap[acid] = id; // kDebug(2) << 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; // kDebug(2) << 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(); //include the budget account only if it is included in the report if (m_config_f.includes(splitAccount)) { MyMoneyAccount::accountTypeE type = splitAccount.accountGroup(); QString outergroup = KMyMoneyUtils::accountTypeToString(type); // reverse sign to match common notation for cash flow direction, only for expense/income splits MyMoneyMoney reverse((splitAccount.accountType() == MyMoneyAccount::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 = 1; // 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); 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_f.columnType() == MyMoneyReport::Column::BiMonths || m_config_f.columnType() == MyMoneyReport::Column::Months || m_config_f.columnType() == MyMoneyReport::Column::Years || m_config_f.columnType() == MyMoneyReport::Column::Quarters) { 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_f.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_f.columnType()) { case MyMoneyReport::Column::Years: case MyMoneyReport::Column::BiMonths: case MyMoneyReport::Column::Quarters: case MyMoneyReport::Column::Months: { 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_f.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); int fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction(); 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 = 1; 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_f.isSkippingZero()); for (int i = 0; i < m_rowTypeList.size(); ++i) { if (m_rowTypeList[i] != eAverage) { //calculate base value MyMoneyMoney oldval = it_row.value()[ m_rowTypeList[i] ][column]; MyMoneyMoney value = (oldval * conversionfactor).reduce(); //convert to lowest fraction it_row.value()[ m_rowTypeList[i] ][column] = PivotCell(value.convert(fraction)); DEBUG_OUTPUT_IF(conversionfactor != MyMoneyMoney::ONE , QString("Factor of %1, value was %2, now %3").arg(conversionfactor.toDouble()).arg(DEBUG_SENSITIVE(oldval.toDouble())).arg(DEBUG_SENSITIVE(it_row.value()[m_rowTypeList[i]][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()) { int column = 1; 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_f.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_f.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.toDouble()).arg(DEBUG_SENSITIVE(oldval.toDouble())).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 // int column = 1; 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 // int column = 1; 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_f.rowType() == MyMoneyReport::Row::ExpenseIncome); const bool invert_total = (*it_outergroup).m_inverted; int column = 1; 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 // int totalcolumn = 1; 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_f.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; } // 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_f.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_f.isColumnsAreDays()) return (m_beginDate.daysTo(_date)); else return (_date.year() * 12 + _date.month()); } QDate PivotTable::columnDate(int column) const { if (m_config_f.isColumnsAreDays()) return m_beginDate.addDays(m_config_f.columnPitch() * column - 1); else return m_beginDate.addMonths(m_config_f.columnPitch() * column).addDays(-1); } QString PivotTable::renderCSV() const { DEBUG_ENTER(Q_FUNC_INFO); // // Report Title // QString result = QString("\"Report: %1\"\n").arg(m_config_f.name()); result += i18nc("Report date range", "%1 through %2\n", KGlobal::locale()->formatDate(m_config_f.fromDate(), KLocale::ShortDate), KGlobal::locale()->formatDate(m_config_f.toDate(), KLocale::ShortDate)); if (m_config_f.isConvertCurrency()) result += i18n("All currencies converted to %1\n", MyMoneyFile::instance()->baseCurrency().name()); else result += i18n("All values shown in %1 unless otherwise noted\n", MyMoneyFile::instance()->baseCurrency().name()); // // Table Header // result += i18n("Account"); int column = 1; 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_f.isShowingRowTotals()) result += QString(",%1").arg(i18nc("Total balance", "Total")); result += '\n'; // Row Type Header if (m_rowTypeList.size() > 1) { int column = 1; while (column < m_numColumns) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString(",%1").arg(m_columnTypeHeaderList[i]); } column++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString(",%1").arg(m_columnTypeHeaderList[i]); } } result += '\n'; } int fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction(); // // Outer groups // // iterate over outer groups PivotGrid::const_iterator it_outergroup = m_grid.begin(); while (it_outergroup != m_grid.end()) { // // Outer Group Header // 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(); int fraction = rowname.currency().smallestAccountFraction(); // // Columns // QString rowdata; int column = 1; bool isUsed = false; for (int i = 0; i < m_rowTypeList.size(); ++i) isUsed |= it_row.value()[ m_rowTypeList[i] ][0].isUsed(); while (column < m_numColumns) { //show columns for (int i = 0; i < m_rowTypeList.size(); ++i) { isUsed |= it_row.value()[ m_rowTypeList[i] ][column].isUsed(); rowdata += QString(",\"%1\"").arg(it_row.value()[ m_rowTypeList[i] ][column].formatMoney(fraction, false)); } column++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) rowdata += QString(",\"%1\"").arg((*it_row)[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false)); } // // 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_f.isConvertCurrency() && rowname.isForeignCurrency()) innergroupdata += QString(" (%1)").arg(rowname.currencyId()); innergroupdata += '\"'; if (isUsed) innergroupdata += rowdata; innergroupdata += '\n'; } ++it_row; } // // Inner Row Group Totals // bool finishrow = true; QString finalRow; bool isUsed = false; if (m_config_f.detailLevel() == MyMoneyReport::DetailLevel::All && ((*it_innergroup).size() > 1)) { // Print the individual rows result += innergroupdata; if (m_config_f.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_f.isConvertCurrency() && rowname.isForeignCurrency()) finalRow += QString(" (%1)").arg(rowname.currencyId()); finalRow += "\""; } // Finish the row started above, unless told not to if (finishrow) { int column = 1; 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(fraction, false)); } column++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) finalRow += QString(",\"%1\"").arg((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false)); } finalRow += '\n'; } if (isUsed) { result += finalRow; ++rownum; } ++it_innergroup; } // // Outer Row Group Totals // if (m_config_f.isShowingColumnTotals()) { result += QString("%1 %2").arg(i18nc("Total balance", "Total")).arg(it_outergroup.key()); int column = 1; 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(fraction, false)); column++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) result += QString(",\"%1\"").arg((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false)); } result += '\n'; } ++it_outergroup; } // // Report Totals // if (m_config_f.isShowingColumnTotals()) { result += i18n("Grand Total"); int totalcolumn = 1; 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(fraction, false)); totalcolumn++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) result += QString(",\"%1\"").arg(m_grid.m_total[ m_rowTypeList[i] ].m_total.formatMoney(fraction, false)); } result += '\n'; } return result; } QString PivotTable::renderBody() const { DEBUG_ENTER(Q_FUNC_INFO); QString colspan = QString(" colspan=\"%1\"").arg(m_numColumns + 1 + (m_config_f.isShowingRowTotals() ? 1 : 0)); // // Report Title // QString result = QString("

%1

\n").arg(m_config_f.name()); //actual dates of the report result += QString("
"); result += i18nc("Report date range", "%1 through %2", KGlobal::locale()->formatDate(m_config_f.fromDate(), KLocale::ShortDate), KGlobal::locale()->formatDate(m_config_f.toDate(), KLocale::ShortDate)); result += QString("
\n"); result += QString("
 
\n"); //currency conversion message result += QString("
"); if (m_config_f.isConvertCurrency()) result += i18n("All currencies converted to %1", MyMoneyFile::instance()->baseCurrency().name()); else result += i18n("All values shown in %1 unless otherwise noted", MyMoneyFile::instance()->baseCurrency().name()); result += QString("
\n"); result += QString("
 
\n"); // setup a leftborder for better readability of budget vs actual reports QString leftborder; if (m_rowTypeList.size() > 1) leftborder = " class=\"leftborder\""; // // Table Header // result += QString("\n\n\n" "\n").arg(i18n("Account")); QString headerspan; int span = m_rowTypeList.size(); headerspan = QString(" colspan=\"%1\"").arg(span); int column = 1; while (column < m_numColumns) result += QString("%2").arg(headerspan, QString(m_columnHeadings[column++]).replace(QRegExp(" "), "
")); if (m_config_f.isShowingRowTotals()) result += QString("%2").arg(headerspan).arg(i18nc("Total balance", "Total")); result += "
\n"; // // Header for multiple columns // if (span > 1) { result += ""; int column = 1; while (column < m_numColumns) { QString lb; if (column != 1) 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_f.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_f.detailLevel() != MyMoneyReport::DetailLevel::Total) { // // 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 // 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_f.detailLevel() != MyMoneyReport::DetailLevel::Group) { // // 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; int column = 1; bool isUsed = it_row.value()[eActual][0].isUsed(); while (column < m_numColumns) { QString lb; if (column != 1) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { rowdata += QString("%1") .arg(coloredAmount(it_row.value()[ m_rowTypeList[i] ][column])) .arg(i == 0 ? lb : QString()); isUsed |= it_row.value()[ m_rowTypeList[i] ][column].isUsed(); } column++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { rowdata += QString("%1") .arg(coloredAmount(it_row.value()[ m_rowTypeList[i] ].m_total)) .arg(i == 0 ? leftborder : QString()); } } // // 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_f.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"; } ++it_row; } // // Inner Row Group Totals // bool finishrow = true; QString finalRow; bool isUsed = false; if (m_config_f.detailLevel() == MyMoneyReport::DetailLevel::All && ((*it_innergroup).size() > 1)) { // Print the individual rows result += innergroupdata; if (m_config_f.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_f.detailLevel() == MyMoneyReport::DetailLevel::All ? "id=\"solo\"" : "") .arg(rowname.hierarchyDepth() - 1) .arg(rowname.name().replace(QRegExp(" "), " ")) .arg((m_config_f.isConvertCurrency() || !rowname.isForeignCurrency()) ? QString() : QString(" (%1)").arg(rowname.currency().id())); } // Finish the row started above, unless told not to if (finishrow) { int column = 1; isUsed |= (*it_innergroup).m_total[eActual][0].isUsed(); while (column < m_numColumns) { QString lb; if (column != 1) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { finalRow += QString("%1") .arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ][column])) .arg(i == 0 ? lb : QString()); isUsed |= (*it_innergroup).m_total[ m_rowTypeList[i] ][column].isUsed(); } column++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { finalRow += QString("%1") .arg(coloredAmount((*it_innergroup).m_total[ m_rowTypeList[i] ].m_total)) .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_f.isShowingColumnTotals()) { result += QString("").arg(i18nc("Total balance", "Total")).arg((*it_outergroup).m_displayName); int column = 1; while (column < m_numColumns) { QString lb; if (column != 1) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ][column])) .arg(i == 0 ? lb : QString()); } column++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount((*it_outergroup).m_total[ m_rowTypeList[i] ].m_total)) .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_f.isShowingColumnTotals()) { result += QString("\n"); result += QString("").arg(i18n("Grand Total")); int totalcolumn = 1; while (totalcolumn < m_numColumns) { QString lb; if (totalcolumn != 1) lb = leftborder; for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ][totalcolumn])) .arg(i == 0 ? lb : QString()); } totalcolumn++; } if (m_config_f.isShowingRowTotals()) { for (int i = 0; i < m_rowTypeList.size(); ++i) { result += QString("%1") .arg(coloredAmount(m_grid.m_total[ m_rowTypeList[i] ].m_total)) .arg(i == 0 ? leftborder : QString()); } } result += "\n"; } result += QString("\n"); result += QString("\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) << renderBody(); g.close(); } void PivotTable::drawChart(KReportChartView& chartView) const { chartView.drawPivotChart(m_grid, m_config_f, m_numColumns, m_columnHeadings, m_rowTypeList, m_columnTypeHeaderList); } QString PivotTable::coloredAmount(const MyMoneyMoney& amount, const QString& currencySymbol, int prec) const { QString result; if (amount.isNegative()) result += QString("") .arg(KMyMoneyGlobalSettings::listNegativeValueColor().red()) .arg(KMyMoneyGlobalSettings::listNegativeValueColor().green()) .arg(KMyMoneyGlobalSettings::listNegativeValueColor().blue()); result += amount.formatMoney(currencySymbol, prec); if (amount.isNegative()) result += QString(""); return result; } 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 = 1; switch (it_row.key().accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Asset: while (column < m_numColumns) { it_row.value()[eBudgetDiff][column] = it_row.value()[eActual][column] - it_row.value()[eBudget][column]; ++column; } break; case MyMoneyAccount::Expense: case MyMoneyAccount::Liability: while (column < m_numColumns) { it_row.value()[eBudgetDiff][column] = 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_f.rowType() == MyMoneyReport::Row::AssetLiability) { //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 = 1; QDate forecastDate = m_beginDate; //check whether columns are days or months if (m_config_f.isColumnsAreDays()) { while (column < m_numColumns) { it_row.value()[eForecast][column] = 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); forecastDate = forecastDate.addMonths(1); ++column; } } ++it_row; } ++it_innergroup; } ++it_outergroup; } } void PivotTable::loadRowTypeList() { if ((m_config_f.isIncludingBudgetActuals()) || (!m_config_f.hasBudget() && !m_config_f.isIncludingForecast() && !m_config_f.isIncludingMovingAverage() && !m_config_f.isIncludingPrice() && !m_config_f.isIncludingAveragePrice()) ) { m_rowTypeList.append(eActual); m_columnTypeHeaderList.append(i18n("Actual")); } if (m_config_f.hasBudget()) { m_rowTypeList.append(eBudget); m_columnTypeHeaderList.append(i18n("Budget")); } if (m_config_f.isIncludingBudgetActuals()) { m_rowTypeList.append(eBudgetDiff); m_columnTypeHeaderList.append(i18n("Difference")); } if (m_config_f.isIncludingForecast()) { m_rowTypeList.append(eForecast); m_columnTypeHeaderList.append(i18n("Forecast")); } if (m_config_f.isIncludingMovingAverage()) { m_rowTypeList.append(eAverage); m_columnTypeHeaderList.append(i18n("Moving Average")); } if (m_config_f.isIncludingAveragePrice()) { m_rowTypeList.append(eAverage); m_columnTypeHeaderList.append(i18n("Moving Average Price")); } if (m_config_f.isIncludingPrice()) { m_rowTypeList.append(ePrice); m_columnTypeHeaderList.append(i18n("Price")); } } void PivotTable::calculateMovingAverage() { int delta = m_config_f.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 = 1; //check whether columns are days or months if (m_config_f.columnType() == MyMoneyReport::Column::Days) { 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_f.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); ++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_f.columnType()) { case MyMoneyReport::Column::Years: { averageStart = QDate(columnDate(column).year(), 1, 1); break; } case MyMoneyReport::Column::BiMonths: { averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1); break; } case MyMoneyReport::Column::Quarters: { averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1).addMonths(-1); break; } case MyMoneyReport::Column::Months: { averageStart = QDate(columnDate(column).year(), columnDate(column).month(), 1); break; } case MyMoneyReport::Column::Weeks: { 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_f.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); ++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 = 1; //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; } ++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_f.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() == MyMoneyAccount::Investment) { for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) { if (!accountList.contains(*it_b)) { m_config_f.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 = 1; 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 - QDebug operator<<(QDebug dbg, reports::PivotTable &a) { - dbg << "reports::PivotTable(" - << "grid" << a.grid() - << ")"; - return dbg; + return DebugIndenter(dbg, typeid(a).name()) + << "grid" << a.grid(); } diff --git a/kmymoney/reports/reportconfigtest.cpp b/kmymoney/reports/reportconfigtest.cpp new file mode 100644 index 000000000..ea944470b --- /dev/null +++ b/kmymoney/reports/reportconfigtest.cpp @@ -0,0 +1,36 @@ +#include "reportconfigtest.h" + +#include "mymoneyfile.h" +#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragexml.h" +#include "kreportsview.h" + +#include + +class MyTestMoneyStorageXML : public MyMoneyStorageXML { +public: + bool readFile(const QString &filename, IMyMoneyStorage *storage) + { + QFile file(filename); + if (file.open(QIODevice::ReadOnly), true) { + MyMoneyStorageXML::readFile(&file, dynamic_cast(storage)); + file.close(); + return true; + } + return false; + } +}; + +ReportConfigTest::ReportConfigTest(QObject *parent) : QObject(parent) +{ +} + +void ReportConfigTest::init() +{ + IMyMoneyStorage *storage = new MyMoneySeqAccessMgr; + MyTestMoneyStorageXML reader; + m->attachStorage(storage); + QList groups; + KReportsView::defaultReports(groups); + reader.readFile("reporttest.xml", storage); +} diff --git a/kmymoney/reports/reportconfigtest.h b/kmymoney/reports/reportconfigtest.h new file mode 100644 index 000000000..61b0c6b94 --- /dev/null +++ b/kmymoney/reports/reportconfigtest.h @@ -0,0 +1,20 @@ +#ifndef REPORTCONFIGTEST_H +#define REPORTCONFIGTEST_H + +#include +class MyMoneyFile; + +class ReportConfigTest : public QObject +{ + Q_OBJECT +public: + MyMoneyFile *m; + + explicit ReportConfigTest(QObject *parent = nullptr); + + void init(); + +public slots: +}; + +#endif // REPORTCONFIGTEST_H diff --git a/kmymoney/reports/reporttable.cpp b/kmymoney/reports/reporttable.cpp index 31385a4c8..2820597e5 100644 --- a/kmymoney/reports/reporttable.cpp +++ b/kmymoney/reports/reporttable.cpp @@ -1,149 +1,151 @@ /*************************************************************************** reporttable.cpp - description ------------------- begin : Fr Apr 16 2010 copyright : (C) 2010 Bernd Gonsior email : bgo@freeplexx.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. * * * ***************************************************************************/ #include "reporttable.h" +#include "misc/debugindenter.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyglobalsettings.h" reports::ReportTable::ReportTable(): m_resourceHtml("html"), m_reportStyleSheet("reportstylesheet"), m_cssFileDefault("kmymoney.css") { // set up default values m_resourceType = QLatin1String("appdata").latin1(); } QString reports::ReportTable::cssFileNameGet() { QString cssfilename; if (!MyMoneyFile::instance()->value(m_reportStyleSheet).isEmpty()) { // try to find the stylesheet specific for this report cssfilename = KGlobal::dirs()-> findResource(m_resourceType, m_resourceHtml + '/' + MyMoneyFile::instance()-> value(m_reportStyleSheet)); } if (cssfilename.isEmpty()) { // if no report specific stylesheet was found, try to use the configured one cssfilename = KMyMoneyGlobalSettings::cssFileDefault(); } if (cssfilename.isEmpty()) { // if there still is nothing, try to use the installation default cssfilename = KGlobal::dirs()-> findResource(m_resourceType, m_resourceHtml + '/' + m_cssFileDefault); } return cssfilename; } QString reports::ReportTable::renderHeader(const QString& title, bool includeCSS) { QString header = QString("" + "\n\n" + "\n" + "\n" + title + ""; QString cssfilename = cssFileNameGet(); if (includeCSS) { // include css inline QFile cssFile(cssfilename); if (cssFile.open(QIODevice::ReadOnly)) { QTextStream cssStream(&cssFile); header += QString("\n\n"; cssFile.close(); } else { qDebug() << "reports::ReportTable::htmlHeaderGet: could not open file " << cssfilename << " readonly"; } } else { // do not include css inline instead use a link to the css file header += "\n\n"; } header += KMyMoneyUtils::variableCSS(); header += "\n\n"; return header; } QString reports::ReportTable::renderFooter() { return "\n\n"; } QString reports::ReportTable::renderHTML(QWidget* widget, const QByteArray& encoding, const QString& title, bool includeCSS) { m_encoding = encoding; //this render the HEAD tag and sets the correct css file QString html = renderHeader(title, includeCSS); try { //this method is implemented by each concrete class html += renderBody(); } catch (const MyMoneyException &e) { kDebug(2) << "reports::ReportTable::renderHTML(): ERROR " << e.what(); QString error = i18n("There was an error creating your report: \"%1\".\nPlease report this error to the developer's list: kmymoney-devel@kde.org", e.what()); KMessageBox::error(widget, error, i18n("Critical Error")); html += "

" + i18n("Unable to generate report") + "

" + error + "

"; } //this renders a common footer html += renderFooter(); return html; } QDebug operator<<(QDebug dbg, const reports::ReportTable &a) { - dbg << "reports::ReportTable(" - //<< "encoding" << a.m_encoding - //<< "resourceHtml" << a.m_resourceHtml - //<< "reportStyleSheet" << a.m_reportStyleSheet - //<< "cssFileDefault" << a.m_cssFileDefault - << ")"; - return dbg; + return DebugIndenter(dbg, typeid(a).name()); +// dbg << "reports::ReportTable(" +// //<< "encoding" << a.m_encoding +// //<< "resourceHtml" << a.m_resourceHtml +// //<< "reportStyleSheet" << a.m_reportStyleSheet +// //<< "cssFileDefault" << a.m_cssFileDefault +// << ")"; +// return dbg; } diff --git a/kmymoney/reports/reporttable.h b/kmymoney/reports/reporttable.h index 59ee538b5..69c018a80 100644 --- a/kmymoney/reports/reporttable.h +++ b/kmymoney/reports/reporttable.h @@ -1,165 +1,166 @@ /*************************************************************************** reporttable.h ------------------- begin : Mon May 7 2007 copyright : (C) 2007 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 REPORTTABLE_H #define REPORTTABLE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyutils.h" #include "mymoneyfile.h" #include "mymoneyreport.h" namespace reports { class KReportChartView; /** * This class serves as base class definition for the concrete report classes * This class is abstract but it contains common code used by all children classes */ class ReportTable { private: /** * Tries to find a css file for the report. * * Search is done in following order: *
    *
  1. report specific stylesheet *
  2. configured stylesheet *
  3. installation default of stylesheet *
* * @retval css-filename if a css-file was found * @retval empty-string if no css-file was found */ QString cssFileNameGet(); /** * Name of application resource type. * * @see KGlobal::dirs()->findResource() */ const char* m_resourceType; /** * Subdirectory for html-resources of application. * * @see KGlobal::dirs()->findResource() */ QString m_resourceHtml; /** * Notation of @c reportstylesheet as used by: * @code * MyMoneyFile::instance()::value(); * @endcode */ QString m_reportStyleSheet; /** * Filename of default css file. */ QString m_cssFileDefault; /** * Character set encoding for the report. */ QByteArray m_encoding; protected: ReportTable(); /** * Constructs html header. * * @param title html title of report * @param[in] includeCSS flag, whether the generated html has to include the css inline or whether * the css is referenced as a link to a file * @return html header */ QString renderHeader(const QString& title, bool includeCSS); /** * Constructs html footer. * * @return html footer */ QString renderFooter(); /** * Constructs the body of the report. Implemented by the concrete classes * @see PivotTable * @see ListTable * @return QString with the html body of the report */ virtual QString renderBody() const = 0; public: virtual ~ReportTable() {} /** * Constructs a comma separated-file of the report. Implemented by the concrete classes * @see PivotTable * @see ListTable */ virtual QString renderCSV() const = 0; /** * Renders a graph from the report. Implemented by the concrete classes * @see PivotTable */ virtual void drawChart(KReportChartView& view) const = 0; virtual void dump(const QString& file, const QString& context = QString()) const = 0; /** * Creates the complete html document. * * @param widget parent widget * @param encoding character set encoding * @param title html title of report * @param includeCSS flag, whether the generated html has * to include the css inline or whether * the css is referenced as a link to a file * * @return complete html document */ QString renderHTML(QWidget* widget, const QByteArray& encoding, const QString& title, bool includeCSS = false); }; } -QDebug operator<<(QDebug dbg, const reports::ReportTable& a); + +KMM_MYMONEY_EXPORT QDebug operator<<(QDebug dbg, const reports::ReportTable& a); #endif // REPORTTABLE_H diff --git a/kmymoney/views/khomeview.cpp b/kmymoney/views/khomeview.cpp index 399150c3f..f5c55080c 100644 --- a/kmymoney/views/khomeview.cpp +++ b/kmymoney/views/khomeview.cpp @@ -1,2025 +1,2028 @@ /*************************************************************************** khomeview.cpp - description ------------------- begin : Tue Jan 22 2002 copyright : (C) 2000-2002 by Michael Edwardes 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. * * * ***************************************************************************/ #include "khomeview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyutils.h" #include "kwelcomepage.h" #include "kmymoneyglobalsettings.h" #include "mymoneyfile.h" #include "mymoneyforecast.h" #include "kmymoney.h" #include "kreportchartview.h" #include "pivottable.h" #include "pivotgrid.h" #include "reportaccount.h" #define VIEW_LEDGER "ledger" #define VIEW_SCHEDULE "schedule" #define VIEW_WELCOME "welcome" #define VIEW_HOME "home" #define VIEW_REPORTS "reports" bool accountNameLess(const MyMoneyAccount &acc1, const MyMoneyAccount &acc2) { return acc1.name().localeAwareCompare(acc2.name()) < 0; } using namespace reports; class KHomeView::Private { public: Private() : m_showAllSchedules(false), m_needReload(false), m_netWorthGraphLastValidSize(400, 300) { } /** * daily balances of an account */ typedef QMap dailyBalances; KHTMLPart* m_part; QString m_html; bool m_showAllSchedules; bool m_needReload; MyMoneyForecast m_forecast; MyMoneyMoney m_total; /** * Hold the last valid size of the net worth graph * for the times when the needed size can't be computed. */ QSize m_netWorthGraphLastValidSize; /** * daily forecast balance of accounts */ QMap m_accountList; }; /** * @brief Converts a QPixmap to an data URI scheme * * According to RFC 2397 * * @param pixmap Source to convert * @return full data URI */ QString QPixmapToDataUri(const QPixmap& pixmap) { QImage image(pixmap.toImage()); QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // writes the image in PNG format inside the buffer return QLatin1String("data:image/png;base64,") + QString(byteArray.toBase64()); } KHomeView::KHomeView(QWidget *parent, const char *name) : KMyMoneyViewBase(parent, name, i18n("Home")), d(new Private) { d->m_part = new KHTMLPart(this); addWidget(d->m_part->view()); d->m_part->begin(); d->m_part->write(KWelcomePage::welcomePage()); d->m_part->end(); // we are going to handle the zoom view signal to change the font scale connect(d->m_part->view(), SIGNAL(zoomView(int)), this, SLOT(slotZoomView(int))); disconnect(d->m_part->view(), SIGNAL(zoomView(int)), d->m_part, SLOT(slotZoomView(int))); connect(d->m_part->browserExtension(), SIGNAL(openUrlRequest(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)), this, SLOT(slotOpenUrl(KUrl,KParts::OpenUrlArguments,KParts::BrowserArguments))); } KHomeView::~KHomeView() { // if user wants to remember the font size, store it here if (KMyMoneyGlobalSettings::rememberFontSize()) { KMyMoneyGlobalSettings::setFontSizePercentage(d->m_part->fontScaleFactor()); KMyMoneyGlobalSettings::self()->writeConfig(); } //This is to prevent a crash on exit with KDE 4.3.2 delete d->m_part; delete d; } void KHomeView::slotLoadView() { d->m_needReload = true; if (isVisible()) { loadView(); d->m_needReload = false; } } void KHomeView::showEvent(QShowEvent* event) { emit aboutToShow(); if (d->m_needReload) { loadView(); d->m_needReload = false; } QWidget::showEvent(event); } void KHomeView::slotPrintView() { if (d->m_part && d->m_part->view()) d->m_part->view()->print(); } void KHomeView::slotZoomView(int delta) { const int fontScaleStepping = 10; if (delta > 0) d->m_part->setFontScaleFactor(d->m_part->fontScaleFactor() + fontScaleStepping); else d->m_part->setFontScaleFactor(d->m_part->fontScaleFactor() - fontScaleStepping); } void KHomeView::loadView() { d->m_part->setFontScaleFactor(KMyMoneyGlobalSettings::fontSizePercentage()); QList list; MyMoneyFile::instance()->accountList(list); if (list.count() == 0) { d->m_part->begin(); d->m_part->write(KWelcomePage::welcomePage()); d->m_part->end(); } else { //clear the forecast flag so it will be reloaded d->m_forecast.setForecastDone(false); const QString filename = KGlobal::dirs()->findResource("appdata", "html/kmymoney.css"); QString header = QString("\n\n").arg(filename); header += KMyMoneyUtils::variableCSS(); header += "\n"; QString footer = "\n"; d->m_html.clear(); d->m_html += header; d->m_html += QString("
%1
").arg(i18n("Your Financial Summary")); QStringList settings = KMyMoneyGlobalSettings::itemList(); QStringList::ConstIterator it; for (it = settings.constBegin(); it != settings.constEnd(); ++it) { int option = (*it).toInt(); if (option > 0) { switch (option) { case 1: // payments showPayments(); break; case 2: // preferred accounts showAccounts(Preferred, i18n("Preferred Accounts")); break; case 3: // payment accounts // Check if preferred accounts are shown separately if (settings.contains("2")) { showAccounts(static_cast(Payment | Preferred), i18n("Payment Accounts")); } else { showAccounts(Payment, i18n("Payment Accounts")); } break; case 4: // favorite reports showFavoriteReports(); break; case 5: // forecast showForecast(); break; case 6: // net worth graph over all accounts showNetWorthGraph(); break; case 8: // assets and liabilities showAssetsLiabilities(); break; case 9: // budget showBudget(); break; case 10: // cash flow summary showCashFlowSummary(); break; } d->m_html += "
 
\n"; } } d->m_html += "
"; d->m_html += link(VIEW_WELCOME, QString()) + i18n("Show KMyMoney welcome page") + linkend(); d->m_html += "
"; d->m_html += "
"; d->m_html += footer; d->m_part->begin(); d->m_part->write(d->m_html); d->m_part->end(); } } void KHomeView::showNetWorthGraph() { d->m_html += QString("
%1
\n
 
\n").arg(i18n("Net Worth Forecast")); MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::Row::AssetLiability, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::userDefined, // overridden by the setDateFilter() call below MyMoneyReport::DetailLevel::Total, i18n("Net Worth Forecast"), i18n("Generated Report")); reportCfg.setChartByDefault(true); reportCfg.setChartGridLines(false); reportCfg.setChartDataLabels(false); reportCfg.setChartType(MyMoneyReport::Chart::Line); reportCfg.setIncludingSchedules(false); reportCfg.addAccountGroup(MyMoneyAccount::Asset); reportCfg.addAccountGroup(MyMoneyAccount::Liability); reportCfg.setColumnsAreDays(true); reportCfg.setConvertCurrency(true); reportCfg.setIncludingForecast(true); reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(+ 90)); reports::PivotTable table(reportCfg); + + qDebug() << reportCfg; + qDebug() << table; reports::KReportChartView* chartWidget = new reports::KReportChartView(0); table.drawChart(*chartWidget); // Adjust the size QSize netWorthGraphSize = KHomeView::size(); netWorthGraphSize -= QSize(80, 30); // consider the computed size valid only if it's smaller on both axes that the applications size if (netWorthGraphSize.width() < kmymoney->width() || netWorthGraphSize.height() < kmymoney->height()) { d->m_netWorthGraphLastValidSize = netWorthGraphSize; } chartWidget->resize(d->m_netWorthGraphLastValidSize); //save the chart to an image QString chart = QPixmapToDataUri(QPixmap::grabWidget(chartWidget->coordinatePlane()->parent())); d->m_html += QString(""); d->m_html += QString(""); d->m_html += QString("").arg(chart); d->m_html += QString(""); d->m_html += QString("
\"Networth\"
"); //delete the widget since we no longer need it delete chartWidget; } void KHomeView::showPayments() { MyMoneyFile* file = MyMoneyFile::instance(); QList overdues; QList schedule; int i = 0; //if forecast has not been executed yet, do it. if (!d->m_forecast.isForecastDone()) doForecast(); schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate::currentDate(), QDate::currentDate().addMonths(1)); overdues = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true); if (schedule.empty() && overdues.empty()) return; // HACK // Remove the finished schedules QList::Iterator d_it; //regular schedules d_it = schedule.begin(); while (d_it != schedule.end()) { if ((*d_it).isFinished()) { d_it = schedule.erase(d_it); continue; } ++d_it; } //overdue schedules d_it = overdues.begin(); while (d_it != overdues.end()) { if ((*d_it).isFinished()) { d_it = overdues.erase(d_it); continue; } ++d_it; } d->m_html += "
"; d->m_html += QString("
%1
\n").arg(i18n("Payments")); if (!overdues.isEmpty()) { d->m_html += "
 
\n"; qSort(overdues); QList::Iterator it; QList::Iterator it_f; d->m_html += ""; d->m_html += QString("\n").arg(showColoredAmount(i18n("Overdue payments"), true)); d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; for (it = overdues.begin(); it != overdues.end(); ++it) { // determine number of overdue payments int cnt = (*it).transactionsRemainingUntil(QDate::currentDate().addDays(-1)); d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it, cnt); d->m_html += ""; } d->m_html += "
%1
"; d->m_html += i18n("Date"); d->m_html += ""; d->m_html += i18n("Schedule"); d->m_html += ""; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Amount"); d->m_html += ""; d->m_html += i18n("Balance after"); d->m_html += "
"; } if (!schedule.isEmpty()) { qSort(schedule); // Extract todays payments if any QList todays; QList::Iterator t_it; for (t_it = schedule.begin(); t_it != schedule.end();) { if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { todays.append(*t_it); (*t_it).setNextDueDate((*t_it).nextPayment(QDate::currentDate())); // if adjustedNextDueDate is still currentDate then remove it from // scheduled payments if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { t_it = schedule.erase(t_it); continue; } } ++t_it; } if (todays.count() > 0) { d->m_html += "
 
\n"; d->m_html += ""; d->m_html += QString("\n").arg(i18n("Today's due payments")); d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; for (t_it = todays.begin(); t_it != todays.end(); ++t_it) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*t_it); d->m_html += ""; } d->m_html += "
%1
"; d->m_html += i18n("Date"); d->m_html += ""; d->m_html += i18n("Schedule"); d->m_html += ""; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Amount"); d->m_html += ""; d->m_html += i18n("Balance after"); d->m_html += "
"; } if (!schedule.isEmpty()) { d->m_html += "
 
\n"; QList::Iterator it; d->m_html += ""; d->m_html += QString("\n").arg(i18n("Future payments")); d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; // show all or the first 6 entries int cnt; cnt = (d->m_showAllSchedules) ? -1 : 6; bool needMoreLess = d->m_showAllSchedules; QDate lastDate = QDate::currentDate().addMonths(1); qSort(schedule); do { it = schedule.begin(); if (it == schedule.end()) break; // if the next due date is invalid (schedule is finished) // we remove it from the list QDate nextDate = (*it).nextDueDate(); if (!nextDate.isValid()) { schedule.erase(it); continue; } if (nextDate > lastDate) break; if (cnt == 0) { needMoreLess = true; break; } // in case we've shown the current recurrence as overdue, // we don't show it here again, but keep the schedule // as it might show up later in the list again if (!(*it).isOverdue()) { if (cnt > 0) --cnt; d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it); d->m_html += ""; // for single occurrence we have reported everything so we // better get out of here. if ((*it).occurrence() == MyMoneySchedule::OCCUR_ONCE) { schedule.erase(it); continue; } } // if nextPayment returns an invalid date, setNextDueDate will // just skip it, resulting in a loop // we check the resulting date and erase the schedule if invalid if (!((*it).nextPayment((*it).nextDueDate())).isValid()) { schedule.erase(it); continue; } (*it).setNextDueDate((*it).nextPayment((*it).nextDueDate())); qSort(schedule); } while (1); if (needMoreLess) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += ""; d->m_html += ""; } d->m_html += "
%1
"; d->m_html += i18n("Date"); d->m_html += ""; d->m_html += i18n("Schedule"); d->m_html += ""; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Amount"); d->m_html += ""; d->m_html += i18n("Balance after"); d->m_html += "
"; if (d->m_showAllSchedules) { d->m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18nc("Less...", "Show fewer schedules on the list") + linkend(); } else { d->m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18nc("More...", "Show more schedules on the list") + linkend(); } d->m_html += "
"; } } d->m_html += "
"; } void KHomeView::showPaymentEntry(const MyMoneySchedule& sched, int cnt) { QString tmp; MyMoneyFile* file = MyMoneyFile::instance(); try { MyMoneyAccount acc = sched.account(); if (!acc.id().isEmpty()) { MyMoneyTransaction t = sched.transaction(); // only show the entry, if it is still active if (!sched.isFinished()) { MyMoneySplit sp = t.splitByAccount(acc.id(), true); QString pathEnter = QPixmapToDataUri(KIconLoader::global()->loadIcon(KMyMoneyGlobalSettings::enterScheduleIcon(), KIconLoader::Small, KIconLoader::SizeSmall)); QString pathSkip = QPixmapToDataUri(KIconLoader::global()->loadIcon(QString("media-seek-forward"), KIconLoader::Small, KIconLoader::SizeSmall)); //show payment date tmp = QString("") + KGlobal::locale()->formatDate(sched.adjustedNextDueDate(), KLocale::ShortDate) + ""; if (!pathEnter.isEmpty()) tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=enter").arg(sched.id()), i18n("Enter schedule")) + QString("").arg(pathEnter) + linkend(); if (!pathSkip.isEmpty()) tmp += " " + link(VIEW_SCHEDULE, QString("?id=%1&mode=skip").arg(sched.id()), i18n("Skip schedule")) + QString("").arg(pathSkip) + linkend(); tmp += QString(" "); tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend(); //show quantity of payments overdue if any if (cnt > 1) tmp += i18np(" (%1 payment)", " (%1 payments)", cnt); //show account of the main split tmp += ""; tmp += QString(file->account(acc.id()).name()); //show amount of the schedule tmp += ""; const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(acc.currencyId()); MyMoneyMoney payment = MyMoneyMoney(sp.value(t.commodity(), acc.currencyId()) * cnt); QString amount = MyMoneyUtils::formatMoney(payment, acc, currency); amount.replace(QChar(' '), " "); tmp += showColoredAmount(amount, payment.isNegative()); tmp += ""; //show balance after payments tmp += ""; QDate paymentDate = QDate(sched.adjustedNextDueDate()); MyMoneyMoney balanceAfter = forecastPaymentBalance(acc, payment, paymentDate); QString balance = MyMoneyUtils::formatMoney(balanceAfter, acc, currency); balance.replace(QChar(' '), " "); tmp += showColoredAmount(balance, balanceAfter.isNegative()); tmp += ""; // qDebug("paymentEntry = '%s'", tmp.toLatin1()); d->m_html += tmp; } } } catch (const MyMoneyException &e) { qDebug("Unable to display schedule entry: %s", qPrintable(e.what())); } } void KHomeView::showAccounts(KHomeView::paymentTypeE type, const QString& header) { MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); QList accounts; bool showClosedAccounts = kmymoney->toggleAction("view_show_all_accounts")->isChecked(); // get list of all accounts file->accountList(accounts); for (QList::Iterator it = accounts.begin(); it != accounts.end();) { bool removeAccount = false; if (!(*it).isClosed() || showClosedAccounts) { switch ((*it).accountType()) { case MyMoneyAccount::Expense: case MyMoneyAccount::Income: // never show a category account // Note: This might be different in a future version when // the homepage also shows category based information removeAccount = true; break; // Asset and Liability accounts are only shown if they // have the preferred flag set case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Investment: // if preferred accounts are requested, then keep in list if ((*it).value("PreferredAccount") != "Yes" || (type & Preferred) == 0) { removeAccount = true; } break; // Check payment accounts. If payment and preferred is selected, // then always show them. If only payment is selected, then // show only if preferred flag is not set. case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::CreditCard: switch (type & (Payment | Preferred)) { case Payment: if ((*it).value("PreferredAccount") == "Yes") removeAccount = true; break; case Preferred: if ((*it).value("PreferredAccount") != "Yes") removeAccount = true; break; case Payment | Preferred: break; default: removeAccount = true; break; } break; // filter all accounts that are not used on homepage views default: removeAccount = true; break; } } else if ((*it).isClosed() || (*it).isInvest()) { // don't show if closed or a stock account removeAccount = true; } if (removeAccount) it = accounts.erase(it); else ++it; } if (!accounts.isEmpty()) { // sort the accounts by name qStableSort(accounts.begin(), accounts.end(), accountNameLess); QString tmp; int i = 0; tmp = "
" + header + "
\n
 
\n"; d->m_html += tmp; d->m_html += ""; d->m_html += ""; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader; KIconLoader::global()->loadIcon(QString("download"), KIconLoader::Small, KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), &pathStatusHeader); d->m_html += QString("").arg(pathStatusHeader); } d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += QString(""); if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += QString(""); if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += QString(""); d->m_html += ""; //only show limit info if user chose to do so if (KMyMoneyGlobalSettings::showLimitInfo()) { d->m_html += ""; } d->m_html += ""; d->m_total = 0; QList::const_iterator it_m; for (it_m = accounts.constBegin(); it_m != accounts.constEnd(); ++it_m) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showAccountEntry(*it_m); d->m_html += ""; } d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); QString amount = d->m_total.formatMoney(file->baseCurrency().tradingSymbol(), prec); if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) d->m_html += ""; d->m_html += QString("").arg(i18n("Total")); if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += ""; d->m_html += QString("").arg(showColoredAmount(amount, d->m_total.isNegative())); d->m_html += "
"; d->m_html += i18n("Account"); d->m_html += "!MC!R"; d->m_html += i18n("Current Balance"); d->m_html += ""; d->m_html += i18n("To Minimum Balance / Maximum Credit"); d->m_html += "
%1%1
"; } } void KHomeView::showAccountEntry(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneySecurity currency = file->currency(acc.currencyId()); MyMoneyMoney value; bool showLimit = KMyMoneyGlobalSettings::showLimitInfo(); if (acc.accountType() == MyMoneyAccount::Investment) { //investment accounts show the balances of all its subaccounts value = investmentBalance(acc); //investment accounts have no minimum balance showAccountEntry(acc, value, MyMoneyMoney(), showLimit); } else { //get balance for normal accounts value = file->balance(acc.id(), QDate::currentDate()); if (acc.currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount(acc.id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(file->baseCurrency().smallestAccountFraction()); d->m_total += baseValue; } else { d->m_total += value; } //if credit card or checkings account, show maximum credit if (acc.accountType() == MyMoneyAccount::CreditCard || acc.accountType() == MyMoneyAccount::Checkings) { QString maximumCredit = acc.value("maxCreditAbsolute"); if (maximumCredit.isEmpty()) { maximumCredit = acc.value("minBalanceAbsolute"); } MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit); showAccountEntry(acc, value, value - maxCredit, showLimit); } else { //otherwise use minimum balance QString minimumBalance = acc.value("minBalanceAbsolute"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); showAccountEntry(acc, value, value - minBalance, showLimit); } } } void KHomeView::showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal) { MyMoneyFile* file = MyMoneyFile::instance(); QString tmp; MyMoneySecurity currency = file->currency(acc.currencyId()); QString amount; QString amountToMinBal; //format amounts amount = MyMoneyUtils::formatMoney(value, acc, currency); amount.replace(QChar(' '), " "); if (showMinBal) { amountToMinBal = MyMoneyUtils::formatMoney(valueToMinBal, acc, currency); amountToMinBal.replace(QChar(' '), " "); } QString cellStatus, cellCounts, pathOK, pathTODO, pathNotOK; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { //show account's online-status KIconLoader::global()->loadIcon(QString("dialog-ok-apply"), KIconLoader::Small, KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), &pathOK); KIconLoader::global()->loadIcon(QString("mail-receive"), KIconLoader::Small, KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), &pathTODO); KIconLoader::global()->loadIcon(QString("dialog-cancel"), KIconLoader::Small, KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), &pathNotOK); if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty()) cellStatus = '-'; else if (file->hasMatchingOnlineBalance(acc)) { if (file->hasNewerTransaction(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate))) cellStatus = QString("").arg(pathTODO); else cellStatus = QString("").arg(pathOK); } else cellStatus = QString("").arg(pathNotOK); tmp = QString("%1").arg(cellStatus); } tmp += QString("") + link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())) + acc.name() + linkend() + ""; int countNotMarked = 0, countCleared = 0, countNotReconciled = 0; QString countStr; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions() || KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) countNotMarked = file->countTransactionsWithSpecificReconciliationState(acc.id(), MyMoneyTransactionFilter::notReconciled); if (KMyMoneyGlobalSettings::showCountOfClearedTransactions() || KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) countCleared = file->countTransactionsWithSpecificReconciliationState(acc.id(), MyMoneyTransactionFilter::cleared); if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) countNotReconciled = countNotMarked + countCleared; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) { if (countNotMarked) countStr = QString("%1").arg(countNotMarked); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) { if (countCleared) countStr = QString("%1").arg(countCleared); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) { if (countNotReconciled) countStr = QString("%1").arg(countNotReconciled); else countStr = '-'; tmp += QString("%1").arg(countStr); } //show account balance tmp += QString("%1").arg(showColoredAmount(amount, value.isNegative())); //show minimum balance column if requested if (showMinBal) { //if it is an investment, show minimum balance empty if (acc.accountType() == MyMoneyAccount::Investment) { tmp += QString(" "); } else { //show minimum balance entry tmp += QString("%1").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative())); } } // qDebug("accountEntry = '%s'", tmp.toLatin1()); d->m_html += tmp; } MyMoneyMoney KHomeView::investmentBalance(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyMoney value; value = file->balance(acc.id(), QDate::currentDate()); QList::const_iterator it_a; for (it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) { MyMoneyAccount stock = file->account(*it_a); if (!stock.isClosed()) { try { MyMoneyMoney val; MyMoneyMoney balance = file->balance(stock.id(), QDate::currentDate()); MyMoneySecurity security = file->security(stock.currencyId()); const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency()); val = (balance * price.rate(security.tradingCurrency())).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())); // 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; } void KHomeView::showFavoriteReports() { QList reports = MyMoneyFile::instance()->reportList(); if (!reports.isEmpty()) { bool firstTime = 1; int row = 0; QList::const_iterator it_report = reports.constBegin(); while (it_report != reports.constEnd()) { if ((*it_report).isFavorite()) { if (firstTime) { d->m_html += QString("
%1
\n
 
\n").arg(i18n("Favorite Reports")); d->m_html += ""; d->m_html += ""; firstTime = false; } d->m_html += QString("") .arg(row++ & 0x01 ? "even" : "odd") .arg(link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id()))) .arg((*it_report).name()) .arg(linkend()) .arg((*it_report).comment()); } ++it_report; } if (!firstTime) d->m_html += "
"; d->m_html += i18n("Report"); d->m_html += ""; d->m_html += i18n("Comment"); d->m_html += "
%2%3%4%5
"; } } void KHomeView::showForecast() { MyMoneyFile* file = MyMoneyFile::instance(); QList accList; //if forecast has not been executed yet, do it. if (!d->m_forecast.isForecastDone()) doForecast(); accList = d->m_forecast.accountList(); if (accList.count() > 0) { // sort the accounts by name qStableSort(accList.begin(), accList.end(), accountNameLess); int i = 0; int colspan = 1; //get begin day int beginDay = QDate::currentDate().daysTo(d->m_forecast.beginForecastDate()); //if begin day is today skip to next cycle if (beginDay == 0) beginDay = d->m_forecast.accountsCycle(); // Now output header d->m_html += QString("
%1
\n
 
\n").arg(i18n("%1 Day Forecast", d->m_forecast.forecastDays())); d->m_html += ""; d->m_html += ""; int colWidth = 55 / (d->m_forecast.forecastDays() / d->m_forecast.accountsCycle()); for (i = 0; (i*d->m_forecast.accountsCycle() + beginDay) <= d->m_forecast.forecastDays(); ++i) { d->m_html += QString(""; colspan++; } d->m_html += ""; // Now output entries i = 0; QList::ConstIterator it_account; for (it_account = accList.constBegin(); it_account != accList.constEnd(); ++it_account) { //MyMoneyAccount acc = (*it_n); d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += QString(""; int dropZero = -1; //account dropped below zero int dropMinimum = -1; //account dropped below minimum balance QString minimumBalance = (*it_account).value("minimumBalance"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); MyMoneySecurity currency; MyMoneyMoney forecastBalance; //change account to deep currency if account is an investment if ((*it_account).isInvest()) { MyMoneySecurity underSecurity = file->security((*it_account).currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security((*it_account).currencyId()); } for (int f = beginDay; f <= d->m_forecast.forecastDays(); f += d->m_forecast.accountsCycle()) { forecastBalance = d->m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f)); QString amount; amount = MyMoneyUtils::formatMoney(forecastBalance, *it_account, currency); amount.replace(QChar(' '), " "); d->m_html += QString("").arg(showColoredAmount(amount, forecastBalance.isNegative())); } d->m_html += ""; //Check if the account is going to be below zero or below the minimal balance in the forecast period //Check if the account is going to be below minimal balance dropMinimum = d->m_forecast.daysToMinimumBalance(*it_account); //Check if the account is going to be below zero in the future dropZero = d->m_forecast.daysToZeroBalance(*it_account); // spit out possible warnings QString msg; // if a minimum balance has been specified, an appropriate warning will // only be shown, if the drop below 0 is on a different day or not present if (dropMinimum != -1 && !minBalance.isZero() && (dropMinimum < dropZero || dropZero == -1)) { switch (dropMinimum) { case -1: break; case 0: msg = i18n("The balance of %1 is below the minimum balance %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; default: msg = i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.", "The balance of %2 will drop below the minimum balance %3 in %1 days.", dropMinimum - 1, (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; } if (!msg.isEmpty()) { d->m_html += QString("").arg(msg).arg(colspan); } } // a drop below zero is always shown msg.clear(); switch (dropZero) { case -1: break; case 0: if ((*it_account).accountGroup() == MyMoneyAccount::Asset) { msg = i18n("The balance of %1 is below %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == MyMoneyAccount::Liability) { msg = i18n("The balance of %1 is above %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } break; default: if ((*it_account).accountGroup() == MyMoneyAccount::Asset) { msg = i18np("The balance of %2 will drop below %3 in %1 day.", "The balance of %2 will drop below %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == MyMoneyAccount::Liability) { msg = i18np("The balance of %2 will raise above %3 in %1 day.", "The balance of %2 will raise above %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } } if (!msg.isEmpty()) { d->m_html += QString("").arg(msg).arg(colspan); } } d->m_html += "
"; d->m_html += i18n("Account"); d->m_html += "").arg(colWidth); d->m_html += i18ncp("Forecast days", "%1 day", "%1 days", i * d->m_forecast.accountsCycle() + beginDay); d->m_html += "
") + link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "").arg(colWidth); d->m_html += QString("%1
%1
%1
"; } } const QString KHomeView::link(const QString& view, const QString& query, const QString& _title) const { QString titlePart; QString title(_title); if (!title.isEmpty()) titlePart = QString(" title=\"%1\"").arg(title.replace(QChar(' '), " ")); return QString("").arg(view, query, titlePart); } const QString KHomeView::linkend() const { return ""; } void KHomeView::slotOpenUrl(const KUrl &url, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&) { QString protocol = url.protocol(); QString view = url.fileName(); QString id = url.queryItem("id"); QString mode = url.queryItem("mode"); if (protocol == "http") { KToolInvocation::invokeBrowser(url.prettyUrl()); } else if (protocol == "mailto") { KToolInvocation::invokeMailer(url); } else { KXmlGuiWindow* mw = KMyMoneyUtils::mainWindow(); Q_CHECK_PTR(mw); if (view == VIEW_LEDGER) { emit ledgerSelected(id, QString()); } else if (view == VIEW_SCHEDULE) { if (mode == "enter") { emit scheduleSelected(id); QTimer::singleShot(0, mw->actionCollection()->action("schedule_enter"), SLOT(trigger())); } else if (mode == "edit") { emit scheduleSelected(id); QTimer::singleShot(0, mw->actionCollection()->action("schedule_edit"), SLOT(trigger())); } else if (mode == "skip") { emit scheduleSelected(id); QTimer::singleShot(0, mw->actionCollection()->action("schedule_skip"), SLOT(trigger())); } else if (mode == "full") { d->m_showAllSchedules = true; loadView(); } else if (mode == "reduced") { d->m_showAllSchedules = false; loadView(); } } else if (view == VIEW_REPORTS) { emit reportSelected(id); } else if (view == VIEW_WELCOME) { if (mode == "whatsnew") { d->m_part->begin(); d->m_part->write(KWelcomePage::whatsNewPage()); d->m_part->end(); } else { d->m_part->begin(); d->m_part->write(KWelcomePage::welcomePage()); d->m_part->end(); } } else if (view == "action") { QTimer::singleShot(0, mw->actionCollection()->action(id), SLOT(trigger())); } else if (view == VIEW_HOME) { QList list; MyMoneyFile::instance()->accountList(list); if (list.count() == 0) { KMessageBox::information(this, i18n("Before KMyMoney can give you detailed information about your financial status, you need to create at least one account. Until then, KMyMoney shows the welcome page instead.")); } loadView(); } else { qDebug("Unknown view '%s' in KHomeView::slotOpenURL()", qPrintable(view)); } } } void KHomeView::showAssetsLiabilities() { QList accounts; QList::ConstIterator it; QList assets; QList liabilities; MyMoneyMoney netAssets; MyMoneyMoney netLiabilities; QString fontStart, fontEnd; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); int i = 0; // get list of all accounts file->accountList(accounts); for (it = accounts.constBegin(); it != accounts.constEnd();) { if (!(*it).isClosed()) { switch ((*it).accountType()) { // group all assets into one list but make sure that investment accounts always show up case MyMoneyAccount::Investment: assets << *it; break; case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::Asset: case MyMoneyAccount::AssetLoan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { assets << *it; } break; // group the liabilities into the other case MyMoneyAccount::CreditCard: case MyMoneyAccount::Liability: case MyMoneyAccount::Loan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { liabilities << *it; } break; default: break; } } ++it; } //only do it if we have assets or liabilities account if (assets.count() > 0 || liabilities.count() > 0) { // sort the accounts by name qStableSort(assets.begin(), assets.end(), accountNameLess); qStableSort(liabilities.begin(), liabilities.end(), accountNameLess); QString statusHeader; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader; KIconLoader::global()->loadIcon(QString("online-banking"), KIconLoader::Small, KIconLoader::SizeSmall, KIconLoader::DefaultState, QStringList(), &pathStatusHeader); statusHeader = QString("").arg(pathStatusHeader); } //print header d->m_html += "
" + i18n("Assets and Liabilities Summary") + "
\n
 
\n"; d->m_html += ""; //column titles d->m_html += ""; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { d->m_html += ""; } d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += ""; d->m_html += ""; //intermediate row to separate both columns d->m_html += ""; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { d->m_html += ""; } d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += ""; d->m_html += ""; QString placeHolder_Status, placeHolder_Counts; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) placeHolder_Status = ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) placeHolder_Counts = ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) placeHolder_Counts += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) placeHolder_Counts += ""; //get asset and liability accounts QList::const_iterator asset_it = assets.constBegin(); QList::const_iterator liabilities_it = liabilities.constBegin(); for (; asset_it != assets.constEnd() || liabilities_it != liabilities.constEnd();) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //write an asset account if we still have any if (asset_it != assets.constEnd()) { MyMoneyMoney value; //investment accounts consolidate the balance of its subaccounts if ((*asset_it).accountType() == MyMoneyAccount::Investment) { value = investmentBalance(*asset_it); } else { value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate()); } //calculate balance for foreign currency accounts if ((*asset_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*asset_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netAssets += baseValue; } else { netAssets += value; } //show the account without minimum balance showAccountEntry(*asset_it, value, MyMoneyMoney(), false); ++asset_it; } else { //write a white space if we don't d->m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } //leave the intermediate column empty d->m_html += ""; //write a liability account if (liabilities_it != liabilities.constEnd()) { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*liabilities_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*liabilities_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netLiabilities += baseValue; } else { netLiabilities += value; } //show the account without minimum balance showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false); ++liabilities_it; } else { //leave the space empty if we run out of liabilities d->m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } d->m_html += ""; } //calculate net worth MyMoneyMoney netWorth = netAssets + netLiabilities; //format assets, liabilities and net worth QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountAssets.replace(QChar(' '), " "); amountLiabilities.replace(QChar(' '), " "); amountNetWorth.replace(QChar(' '), " "); d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //print total for assets d->m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Assets")).arg(placeHolder_Counts).arg(showColoredAmount(amountAssets, netAssets.isNegative())); //leave the intermediate column empty d->m_html += ""; //print total liabilities d->m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Liabilities")).arg(placeHolder_Counts).arg(showColoredAmount(amountLiabilities, netLiabilities.isNegative())); d->m_html += ""; //print net worth d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); d->m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Net Worth")).arg(placeHolder_Counts).arg(showColoredAmount(amountNetWorth, netWorth.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += statusHeader; d->m_html += ""; d->m_html += i18n("Asset Accounts"); d->m_html += "!MC!R"; d->m_html += i18n("Current Balance"); d->m_html += ""; d->m_html += statusHeader; d->m_html += ""; d->m_html += i18n("Liability Accounts"); d->m_html += "!MC!R"; d->m_html += i18n("Current Balance"); d->m_html += "
%2%4%2%4
%2%4
"; d->m_html += "
"; } } void KHomeView::showBudget() { MyMoneyFile* file = MyMoneyFile::instance(); if (file->countBudgets()) { int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); bool isOverrun = false; int i = 0; //config report just like "Monthly Budgeted vs Actual MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::Row::BudgetActual, MyMoneyReport::Column::Months, MyMoneyTransactionFilter::currentMonth, MyMoneyReport::DetailLevel::All, i18n("Monthly Budgeted vs. Actual"), i18n("Generated Report")); reportCfg.setBudget("Any", true); reports::PivotTable table(reportCfg); PivotGrid grid = table.grid(); //div header d->m_html += "
" + i18n("Budget") + "
\n
 
\n"; //display budget summary d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += QString(""); MyMoneyMoney totalBudgetValue = grid.m_total[eBudget].m_total; MyMoneyMoney totalActualValue = grid.m_total[eActual].m_total; MyMoneyMoney totalBudgetDiffValue = grid.m_total[eBudgetDiff].m_total; QString totalBudgetAmount = totalBudgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalActualAmount = totalActualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalBudgetDiffAmount = totalBudgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); d->m_html += QString("").arg(showColoredAmount(totalBudgetAmount, totalBudgetValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(totalActualAmount, totalActualValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(totalBudgetDiffAmount, totalBudgetDiffValue.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Current Month Summary"); d->m_html += "
"; d->m_html += i18n("Budgeted"); d->m_html += ""; d->m_html += i18n("Actual"); d->m_html += ""; d->m_html += i18n("Difference"); d->m_html += "
%1%1%1
"; //budget overrun d->m_html += "
 
\n"; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; PivotGrid::iterator it_outergroup = grid.begin(); while (it_outergroup != grid.end()) { i = 0; 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()) { //column number is 1 because the report includes only current month if (it_row.value()[eBudgetDiff][1].isNegative()) { //get report account to get the name later ReportAccount rowname = it_row.key(); //write the outergroup if it is the first row of outergroup being shown if (i == 0) { d->m_html += ""; d->m_html += QString("").arg(KMyMoneyUtils::accountTypeToString(rowname.accountType())); d->m_html += ""; } d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //get values from grid MyMoneyMoney actualValue = it_row.value()[eActual][1]; MyMoneyMoney budgetValue = it_row.value()[eBudget][1]; MyMoneyMoney budgetDiffValue = it_row.value()[eBudgetDiff][1]; //format amounts QString actualAmount = actualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetAmount = budgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetDiffAmount = budgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); //account name d->m_html += QString(""; //show amounts d->m_html += QString("").arg(showColoredAmount(budgetAmount, budgetValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(actualAmount, actualValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(budgetDiffAmount, budgetDiffValue.isNegative())); d->m_html += ""; //set the flag that there are overruns isOverrun = true; } ++it_row; } ++it_innergroup; } ++it_outergroup; } //if no negative differences are found, then inform that if (!isOverrun) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += QString("").arg(i18n("No Budget Categories have been overrun")); d->m_html += ""; } d->m_html += "
"; d->m_html += i18n("Budget Overruns"); d->m_html += "
"; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Budgeted"); d->m_html += ""; d->m_html += i18n("Actual"); d->m_html += ""; d->m_html += i18n("Difference"); d->m_html += "
%1
") + link(VIEW_LEDGER, QString("?id=%1").arg(rowname.id())) + rowname.name() + linkend() + "%1%1%1
%1
"; } } QString KHomeView::showColoredAmount(const QString& amount, bool isNegative) { if (isNegative) { //if negative, get the settings for negative numbers return QString("%2").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name(), amount); } //if positive, return the same string return amount; } void KHomeView::doForecast() { //clear m_accountList because forecast is about to changed d->m_accountList.clear(); //reinitialize the object d->m_forecast = KMyMoneyGlobalSettings::forecast(); //If forecastDays lower than accountsCycle, adjust to the first cycle if (d->m_forecast.accountsCycle() > d->m_forecast.forecastDays()) d->m_forecast.setForecastDays(d->m_forecast.accountsCycle()); //Get all accounts of the right type to calculate forecast d->m_forecast.doForecast(); } MyMoneyMoney KHomeView::forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate) { //if paymentDate before or equal to currentDate set it to current date plus 1 //so we get to accumulate forecast balance correctly if (paymentDate <= QDate::currentDate()) paymentDate = QDate::currentDate().addDays(1); //check if the account is already there if (d->m_accountList.find(acc.id()) == d->m_accountList.end() || d->m_accountList[acc.id()].find(paymentDate) == d->m_accountList[acc.id()].end()) { if (paymentDate == QDate::currentDate()) { d->m_accountList[acc.id()][paymentDate] = d->m_forecast.forecastBalance(acc, paymentDate); } else { d->m_accountList[acc.id()][paymentDate] = d->m_forecast.forecastBalance(acc, paymentDate.addDays(-1)); } } d->m_accountList[acc.id()][paymentDate] = d->m_accountList[acc.id()][paymentDate] + payment; return d->m_accountList[acc.id()][paymentDate]; } void KHomeView::showCashFlowSummary() { MyMoneyTransactionFilter filter; MyMoneyMoney incomeValue; MyMoneyMoney expenseValue; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); //set start and end of month dates QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1); QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth()); //Add total income and expenses for this month //get transactions for current month filter.setDateFilter(startOfMonth, endOfMonth); filter.setReportAllSplits(false); QList transactions = file->transactionList(filter); //if no transaction then skip and print total in zero if (transactions.size() > 0) { QList::const_iterator it_transaction; //get all transactions for this month for (it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) { //get the splits for each transaction const QList& splits = (*it_transaction).splits(); QList::const_iterator it_split; for (it_split = splits.begin(); it_split != splits.end(); ++it_split) { if (!(*it_split).shares().isZero()) { ReportAccount repSplitAcc = ReportAccount((*it_split).accountId()); //only add if it is an income or expense if (repSplitAcc.isIncomeExpense()) { MyMoneyMoney value; //convert to base currency if necessary if (repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice((*it_transaction).postDate()); value = ((*it_split).shares() * MyMoneyMoney::MINUS_ONE) * curPrice; value = value.convert(10000); } else { value = ((*it_split).shares() * MyMoneyMoney::MINUS_ONE); } //store depending on account type if (repSplitAcc.accountType() == MyMoneyAccount::Income) { incomeValue += value; } else { expenseValue += value; } } } } } } //format income and expenses QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountIncome.replace(QChar(' '), " "); amountExpense.replace(QChar(' '), " "); //calculate schedules //Add all schedules for this month MyMoneyMoney scheduledIncome; MyMoneyMoney scheduledExpense; MyMoneyMoney scheduledLiquidTransfer; MyMoneyMoney scheduledOtherTransfer; //get overdues and schedules until the end of this month QList schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), endOfMonth); //Remove the finished schedules QList::Iterator finished_it; for (finished_it = schedule.begin(); finished_it != schedule.end();) { if ((*finished_it).isFinished()) { finished_it = schedule.erase(finished_it); continue; } ++finished_it; } //add income and expenses QList::Iterator sched_it; for (sched_it = schedule.begin(); sched_it != schedule.end();) { QDate nextDate = (*sched_it).nextDueDate(); int cnt = 0; while (nextDate.isValid() && nextDate <= endOfMonth) { ++cnt; nextDate = (*sched_it).nextPayment(nextDate); // for single occurrence nextDate will not change, so we // better get out of here. if ((*sched_it).occurrence() == MyMoneySchedule::OCCUR_ONCE) break; } MyMoneyAccount acc = (*sched_it).account(); if (!acc.id().isEmpty()) { MyMoneyTransaction transaction = (*sched_it).transaction(); // only show the entry, if it is still active MyMoneySplit sp = transaction.splitByAccount(acc.id(), true); // take care of the autoCalc stuff if ((*sched_it).type() == MyMoneySchedule::TYPE_LOANPAYMENT) { QDate nextDate = (*sched_it).nextPayment((*sched_it).lastPayment()); //make sure we have all 'starting balances' so that the autocalc works QList::const_iterator it_s; QMap balanceMap; for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { MyMoneyAccount acc = file->account((*it_s).accountId()); // collect all overdues on the first day QDate schedDate = nextDate; if (QDate::currentDate() >= nextDate) schedDate = QDate::currentDate().addDays(1); balanceMap[acc.id()] += file->balance(acc.id(), QDate::currentDate()); } KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap); } //go through the splits and assign to liquid or other transfers const QList splits = transaction.splits(); QList::const_iterator split_it; for (split_it = splits.constBegin(); split_it != splits.constEnd(); ++split_it) { if ((*split_it).accountId() != acc.id()) { ReportAccount repSplitAcc = ReportAccount((*split_it).accountId()); //get the shares and multiply by the quantity of occurrences in the period MyMoneyMoney value = (*split_it).shares() * cnt; //convert to foreign currency if needed if (repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice(QDate::currentDate()); value = value * curPrice; value = value.convert(10000); } if ((repSplitAcc.isLiquidLiability() || repSplitAcc.isLiquidAsset()) && acc.accountGroup() != repSplitAcc.accountGroup()) { scheduledLiquidTransfer += value; } else if (repSplitAcc.isAssetLiability() && !repSplitAcc.isLiquidLiability() && !repSplitAcc.isLiquidAsset()) { scheduledOtherTransfer += value; } else if (repSplitAcc.isIncomeExpense()) { //income and expenses are stored as negative values if (repSplitAcc.accountType() == MyMoneyAccount::Income) scheduledIncome -= value; if (repSplitAcc.accountType() == MyMoneyAccount::Expense) scheduledExpense -= value; } } } } ++sched_it; } //format the currency strings QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountScheduledIncome.replace(QChar(' '), " "); amountScheduledExpense.replace(QChar(' '), " "); amountScheduledLiquidTransfer.replace(QChar(' '), " "); amountScheduledOtherTransfer.replace(QChar(' '), " "); //get liquid assets and liabilities QList accounts; QList::const_iterator account_it; MyMoneyMoney liquidAssets; MyMoneyMoney liquidLiabilities; // get list of all accounts file->accountList(accounts); for (account_it = accounts.constBegin(); account_it != accounts.constEnd();) { if (!(*account_it).isClosed()) { switch ((*account_it).accountType()) { //group all assets into one list case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: { MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance for foreign currency accounts if ((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidAssets += baseValue; liquidAssets = liquidAssets.convert(10000); } else { liquidAssets += value; } break; } //group the liabilities into the other case MyMoneyAccount::CreditCard: { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidLiabilities += baseValue; liquidLiabilities = liquidLiabilities.convert(10000); } else { liquidLiabilities += value; } break; } default: break; } } ++account_it; } //calculate net worth MyMoneyMoney liquidWorth = liquidAssets + liquidLiabilities; //format assets, liabilities and net worth QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountLiquidAssets.replace(QChar(' '), " "); amountLiquidLiabilities.replace(QChar(' '), " "); amountLiquidWorth.replace(QChar(' '), " "); //show the summary d->m_html += "
" + i18n("Cash Flow Summary") + "
\n
 
\n"; //print header d->m_html += ""; //income and expense title d->m_html += ""; d->m_html += ""; //column titles d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; //add row with banding d->m_html += QString(""); //print current income d->m_html += QString("").arg(showColoredAmount(amountIncome, incomeValue.isNegative())); //print the scheduled income d->m_html += QString("").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative())); //print current expenses d->m_html += QString("").arg(showColoredAmount(amountExpense, expenseValue.isNegative())); //print the scheduled expenses d->m_html += QString("").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Income and Expenses of Current Month"); d->m_html += "
"; d->m_html += i18n("Income"); d->m_html += ""; d->m_html += i18n("Scheduled Income"); d->m_html += ""; d->m_html += i18n("Expenses"); d->m_html += ""; d->m_html += i18n("Scheduled Expenses"); d->m_html += "
%2%2%2%2
"; //print header of assets and liabilities d->m_html += "
 
\n"; d->m_html += ""; //assets and liabilities title d->m_html += ""; d->m_html += ""; //column titles d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; //add row with banding d->m_html += QString(""); //print current liquid assets d->m_html += QString("").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative())); //print the scheduled transfers d->m_html += QString("").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative())); //print current liabilities d->m_html += QString("").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative())); //print the scheduled transfers d->m_html += QString("").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Liquid Assets and Liabilities"); d->m_html += "
"; d->m_html += i18n("Liquid Assets"); d->m_html += ""; d->m_html += i18n("Transfers to Liquid Liabilities"); d->m_html += ""; d->m_html += i18n("Liquid Liabilities"); d->m_html += ""; d->m_html += i18n("Other Transfers"); d->m_html += "
%2%2%2%2
"; //final conclusion MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense; MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer; MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer; QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountProfit.replace(QChar(' '), " "); amountExpectedAsset.replace(QChar(' '), " "); amountExpectedLiabilities.replace(QChar(' '), " "); //print header of cash flow status d->m_html += "
 
\n"; d->m_html += ""; //income and expense title d->m_html += ""; d->m_html += ""; //column titles d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; //add row with banding d->m_html += QString(""); d->m_html += ""; //print expected assets d->m_html += QString("").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative())); //print expected liabilities d->m_html += QString("").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative())); //print expected profit d->m_html += QString("").arg(showColoredAmount(amountProfit, profitValue.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Cash Flow Status"); d->m_html += "
 "; d->m_html += i18n("Expected Liquid Assets"); d->m_html += ""; d->m_html += i18n("Expected Liquid Liabilities"); d->m_html += ""; d->m_html += i18n("Expected Profit/Loss"); d->m_html += "
 %2%2%2
"; d->m_html += "
"; } // 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 VIEW_LEDGER #undef VIEW_SCHEDULE #undef VIEW_WELCOME #undef VIEW_HOME #undef VIEW_REPORTS