diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp
index 642145a54..336666499 100644
--- a/kmymoney/mymoney/mymoneyreport.cpp
+++ b/kmymoney/mymoney/mymoneyreport.cpp
@@ -1,800 +1,800 @@
/***************************************************************************
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"
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
const QStringList MyMoneyReport::kRowTypeText = 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 QStringList MyMoneyReport::kColumnTypeText = 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 EQueryColumns
// and shift the bit for eQCend one position to the left
-const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance").split(',');
+const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance,capitalgain").split(',');
const MyMoneyReport::EReportType MyMoneyReport::kTypeArray[] = { eNoReport, ePivotTable, ePivotTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, ePivotTable, ePivotTable, eInfoTable, eInfoTable, eInfoTable, eQueryTable, eQueryTable, eNoReport };
const QStringList MyMoneyReport::kDetailLevelText = QString("none,all,top,group,total,invalid").split(',');
const QStringList MyMoneyReport::kChartTypeText = 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(',');
MyMoneyReport::MyMoneyReport() :
m_name("Unconfigured Pivot Table Report"),
m_detailLevel(eDetailNone),
m_convertCurrency(true),
m_favorite(false),
m_tax(false),
m_investments(false),
m_loans(false),
m_reportType(kTypeArray[eExpenseIncome]),
m_rowType(eExpenseIncome),
m_columnType(eMonths),
m_columnsAreDays(false),
m_queryColumns(eQCnone),
m_dateLock(userDefined),
m_accountGroupFilter(false),
m_chartType(eChartLine),
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(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _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(kTypeArray[_rt]),
m_rowType(_rt),
m_columnsAreDays(false),
m_queryColumns(eQCnone),
m_dateLock(_dl),
m_accountGroupFilter(false),
m_chartType(eChartLine),
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 == ePivotTable)
m_columnType = static_cast(_ct);
if (m_reportType == eQueryTable)
m_queryColumns = static_cast(_ct);
setDateFilter(_dl);
//throw exception if the type is inconsistent
if ((_rt > static_cast(sizeof(kTypeArray) / sizeof(kTypeArray[0])))
|| (m_reportType == eNoReport))
throw MYMONEYEXCEPTION("Invalid report type");
//add the corresponding account groups
if (_rt == MyMoneyReport::eAssetLiability) {
addAccountGroup(MyMoneyAccount::Asset);
addAccountGroup(MyMoneyAccount::Liability);
m_showRowTotals = true;
}
if (_rt == MyMoneyReport::eExpenseIncome) {
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::eBudget || _rt == MyMoneyReport::eBudgetActual) {
addAccountGroup(MyMoneyAccount::Expense);
addAccountGroup(MyMoneyAccount::Income);
}
if (_rt == MyMoneyReport::eAccountInfo) {
addAccountGroup(MyMoneyAccount::Asset);
addAccountGroup(MyMoneyAccount::Liability);
}
//cash flow reports show splits for all account groups
if (_rt == MyMoneyReport::eCashFlow) {
addAccountGroup(MyMoneyAccount::Expense);
addAccountGroup(MyMoneyAccount::Income);
addAccountGroup(MyMoneyAccount::Asset);
addAccountGroup(MyMoneyAccount::Liability);
}
}
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(ERowType _rt)
{
m_rowType = _rt;
m_reportType = kTypeArray[_rt];
m_accountGroupFilter = false;
m_accountGroups.clear();
if (_rt == MyMoneyReport::eAssetLiability) {
addAccountGroup(MyMoneyAccount::Asset);
addAccountGroup(MyMoneyAccount::Liability);
}
if (_rt == MyMoneyReport::eExpenseIncome) {
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::eExpenseIncome)
|| 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::eExpenseIncome)
// 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", kRowTypeText[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 >= kChartTypeText.size()) {
qDebug("m_chartType out of bounds with %d on report of type %d. Default to none.", m_chartType, m_reportType);
e.setAttribute("charttype", kChartTypeText[0]);
} else {
e.setAttribute("charttype", kChartTypeText[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 == ePivotTable) {
e.setAttribute("type", "pivottable 1.15");
e.setAttribute("detail", kDetailLevelText[m_detailLevel]);
e.setAttribute("columntype", kColumnTypeText[m_columnType]);
e.setAttribute("showrowtotals", m_showRowTotals);
} else if (m_reportType == eQueryTable) {
e.setAttribute("type", "querytable 1.14");
QStringList columns;
unsigned qc = m_queryColumns;
unsigned it_qc = eQCbegin;
unsigned index = 1;
while (it_qc != eQCend) {
if (qc & it_qc)
columns += kQueryColumnsText[index];
it_qc *= 2;
index++;
}
e.setAttribute("querycolumns", columns.join(","));
} else if (m_reportType == eInfoTable) {
e.setAttribute("type", "infotable 1.0");
e.setAttribute("detail", kDetailLevelText[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::ePivotTable;
} else if (!e.attribute("type").indexOf("querytable")) {
m_reportType = MyMoneyReport::eQueryTable;
} else if (!e.attribute("type").indexOf("infotable")) {
m_reportType = MyMoneyReport::eInfoTable;
} else {
m_reportType = MyMoneyReport::eNoReport;
}
// 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 = kDetailLevelText.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 = eDetailAll;
} else {
//set to show the top level account instead
m_detailLevel = eDetailTop;
}
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 == ePivotTable) {
i = kChartTypeText.indexOf(e.attribute("charttype"));
if (i >= 0)
m_chartType = static_cast(i);
// if it is invalid, set to first type
if (m_chartType >= eChartEnd)
m_chartType = eChartLine;
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 = kRowTypeText.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 == eExpenseIncome)
m_showRowTotals = true;
}
if (e.hasAttribute("showrowtotals"))
m_showRowTotals = e.attribute("showrowtotals").toUInt();
i = kColumnTypeText.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 = kQueryColumnsText.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;
}
diff --git a/kmymoney/mymoney/mymoneyreport.h b/kmymoney/mymoney/mymoneyreport.h
index 7397a0fcb..1596aa32a 100644
--- a/kmymoney/mymoney/mymoneyreport.h
+++ b/kmymoney/mymoney/mymoneyreport.h
@@ -1,697 +1,697 @@
/***************************************************************************
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
enum ERowType { eNoRows = 0, eAssetLiability, eExpenseIncome, eCategory, eTopCategory, eAccount, eTag, ePayee, eMonth, eWeek, eTopAccount, eAccountByTopAccount, eEquityType, eAccountType, eInstitution, eBudget, eBudgetActual, eSchedule, eAccountInfo, eAccountLoanInfo, eAccountReconcile, eCashFlow};
enum EReportType { eNoReport = 0, ePivotTable, eQueryTable, eInfoTable };
enum EColumnType { eNoColumns = 0, eDays = 1, eMonths = 1, eBiMonths = 2, eQuarters = 3, eWeeks = 7, eYears = 12 };
// if you add bits to this bitmask, start with the value currently assigned to eQCend and update its value afterwards
// also don't forget to add column names to kQueryColumnsText in mymoneyreport.cpp
- enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCtag = 0x8, eQCmemo = 0x10, eQCaccount = 0x20, eQCreconciled = 0x40, eQCaction = 0x80, eQCshares = 0x100, eQCprice = 0x200, eQCperformance = 0x400, eQCloan = 0x800, eQCbalance = 0x1000, eQCend = 0x2000 };
+ enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCtag = 0x8, eQCmemo = 0x10, eQCaccount = 0x20, eQCreconciled = 0x40, eQCaction = 0x80, eQCshares = 0x100, eQCprice = 0x200, eQCperformance = 0x400, eQCloan = 0x800, eQCbalance = 0x1000, eQCcapitalgain = 0x2000, eQCend = 0x4000 };
enum EDetailLevel { eDetailNone = 0, eDetailAll, eDetailTop, eDetailGroup, eDetailTotal, eDetailEnd };
enum EChartType { eChartNone = 0, eChartLine, eChartBar, eChartPie, eChartRing, eChartStackedBar, eChartEnd };
static const QStringList kRowTypeText;
static const QStringList kColumnTypeText;
static const QStringList kQueryColumnsText;
static const QStringList kDetailLevelText;
static const QStringList kChartTypeText;
static const EReportType kTypeArray[];
public:
MyMoneyReport();
MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _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);
}
EReportType reportType() const {
return m_reportType;
}
ERowType rowType() const {
return m_rowType;
}
EColumnType columnType() const {
return m_columnType;
}
bool isRunningSum() const {
return (m_rowType == eAssetLiability);
}
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;
}
EQueryColumns 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;
}
EDetailLevel detailLevel() const {
return m_detailLevel;
}
EChartType 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(ERowType _rt);
void setColumnType(EColumnType _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(EQueryColumns _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(EDetailLevel _detail) {
m_detailLevel = _detail;
}
void setChartType(EChartType _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
*/
enum EDetailLevel 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
*/
enum EReportType m_reportType;
/**
* What sort of values should show up on the ROWS of this report
*/
enum ERowType 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.
*/
enum EColumnType 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 EQueryColumns 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
*/
enum EChartType 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)
#endif // MYMONEYREPORT_H
diff --git a/kmymoney/reports/listtable.cpp b/kmymoney/reports/listtable.cpp
index 5f8952878..8b8038243 100644
--- a/kmymoney/reports/listtable.cpp
+++ b/kmymoney/reports/listtable.cpp
@@ -1,718 +1,719 @@
/***************************************************************************
listtable.cpp
-------------------
begin : Sat 28 jun 2008
copyright : (C) 2004-2005 by Ace Jones
2008 by Alvaro Soliverez
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "listtable.h"
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
// This is just needed for i18n(). Once I figure out how to handle i18n
// without using this macro directly, I'll be freed of KDE dependency.
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "mymoneyreport.h"
#include "mymoneyexception.h"
#include "kmymoneyutils.h"
#include "kmymoneyglobalsettings.h"
#include "reportdebug.h"
namespace reports
{
QStringList ListTable::TableRow::m_sortCriteria;
// ****************************************************************************
//
// Group Iterator
//
// ****************************************************************************
class GroupIterator
{
public:
GroupIterator(const QString& _group, const QString& _subtotal, unsigned _depth) : m_depth(_depth), m_groupField(_group), m_subtotalField(_subtotal) {}
GroupIterator() : m_depth(0) {}
void update(const ListTable::TableRow& _row) {
m_previousGroup = m_currentGroup;
m_currentGroup = _row[m_groupField];
if (isSubtotal()) {
m_previousSubtotal = m_currentSubtotal;
m_currentSubtotal = MyMoneyMoney();
}
m_currentSubtotal += MyMoneyMoney(_row[m_subtotalField]);
}
bool isNewHeader() const {
return (m_currentGroup != m_previousGroup);
}
bool isSubtotal() const {
return (m_currentGroup != m_previousGroup) && (!m_previousGroup.isEmpty());
}
const MyMoneyMoney& subtotal() const {
return m_previousSubtotal;
}
const MyMoneyMoney& currenttotal() const {
return m_currentSubtotal;
}
unsigned depth() const {
return m_depth;
}
const QString& name() const {
return m_currentGroup;
}
const QString& oldName() const {
return m_previousGroup;
}
const QString& groupField() const {
return m_groupField;
}
const QString& subtotalField() const {
return m_subtotalField;
}
// ***DV*** HACK make the currentGroup test different but look the same
void force() {
m_currentGroup += ' ';
}
private:
MyMoneyMoney m_currentSubtotal;
MyMoneyMoney m_previousSubtotal;
unsigned m_depth;
QString m_currentGroup;
QString m_previousGroup;
QString m_groupField;
QString m_subtotalField;
};
// ****************************************************************************
//
// ListTable implementation
//
// ****************************************************************************
bool ListTable::TableRow::operator< (const TableRow& _compare) const
{
bool result = false;
QStringList::const_iterator it_criterion = m_sortCriteria.constBegin();
while (it_criterion != m_sortCriteria.constEnd()) {
if (this->operator[](*it_criterion) < _compare[ *it_criterion ]) {
result = true;
break;
} else if (this->operator[](*it_criterion) > _compare[ *it_criterion ])
break;
++it_criterion;
}
return result;
}
// needed for KDE < 3.2 implementation of qHeapSort
bool ListTable::TableRow::operator<= (const TableRow& _compare) const
{
return (!(_compare < *this));
}
bool ListTable::TableRow::operator== (const TableRow& _compare) const
{
return (!(*this < _compare) && !(_compare < *this));
}
bool ListTable::TableRow::operator> (const TableRow& _compare) const
{
return (_compare < *this);
}
/**
* TODO
*
* - Collapse 2- & 3- groups when they are identical
* - Way more test cases (especially splits & transfers)
* - Option to collapse splits
* - Option to exclude transfers
*
*/
ListTable::ListTable(const MyMoneyReport& _report):
ReportTable(),
m_config(_report)
{
}
void ListTable::render(QString& result, QString& csv) const
{
MyMoneyMoney grandtotal;
MyMoneyFile* file = MyMoneyFile::instance();
result = "";
csv = "";
result += QString("%1
\n").arg(m_config.name());
csv += "\"Report: " + m_config.name() + "\"\n";
//actual dates of the report
result += QString("");
if (!m_config.fromDate().isNull()) {
result += i18nc("Report date range", "%1 through %2", QLocale().toString(m_config.fromDate(), QLocale::ShortFormat), QLocale().toString(m_config.toDate(), QLocale::ShortFormat));
result += QString("
\n");
result += QString("
\n");
csv += i18nc("Report date range", "%1 through %2", QLocale().toString(m_config.fromDate(), QLocale::ShortFormat), QLocale().toString(m_config.toDate(), QLocale::ShortFormat));
csv += QString("\n");
}
result += QString("");
if (m_config.isConvertCurrency()) {
result += i18n("All currencies converted to %1" , file->baseCurrency().name());
csv += i18n("All currencies converted to %1\n" , file->baseCurrency().name());
} else {
result += i18n("All values shown in %1 unless otherwise noted" , file->baseCurrency().name());
csv += i18n("All values shown in %1 unless otherwise noted\n" , file->baseCurrency().name());
}
result += QString("
\n");
result += QString("
\n");
// retrieve the configuration parameters from the report definition.
// the things that we care about for query reports are:
// how to group the rows, what columns to display, and what field
// to subtotal on
QStringList groups = m_group.split(',');
QStringList columns = m_columns.split(',');
columns += m_subtotal;
QStringList postcolumns = m_postcolumns.split(',');
columns += postcolumns;
//
// Table header
//
QMap i18nHeaders;
i18nHeaders["postdate"] = i18n("Date");
i18nHeaders["value"] = i18n("Amount");
i18nHeaders["number"] = i18n("Num");
i18nHeaders["payee"] = i18n("Payee");
i18nHeaders["tag"] = i18n("Tags");
i18nHeaders["category"] = i18n("Category");
i18nHeaders["account"] = i18n("Account");
i18nHeaders["memo"] = i18n("Memo");
i18nHeaders["topcategory"] = i18n("Top Category");
i18nHeaders["categorytype"] = i18n("Category Type");
i18nHeaders["month"] = i18n("Month");
i18nHeaders["week"] = i18n("Week");
i18nHeaders["reconcileflag"] = i18n("Reconciled");
i18nHeaders["action"] = i18n("Action");
i18nHeaders["shares"] = i18n("Shares");
i18nHeaders["price"] = i18n("Price");
i18nHeaders["latestprice"] = i18n("Price");
i18nHeaders["netinvvalue"] = i18n("Net Value");
i18nHeaders["buys"] = i18n("Buys");
i18nHeaders["sells"] = i18n("Sells");
i18nHeaders["reinvestincome"] = i18n("Dividends Reinvested");
i18nHeaders["cashincome"] = i18n("Dividends Paid Out");
i18nHeaders["startingbal"] = i18n("Starting Balance");
i18nHeaders["endingbal"] = i18n("Ending Balance");
i18nHeaders["return"] = i18n("Annualized Return");
i18nHeaders["returninvestment"] = i18n("Return On Investment");
i18nHeaders["fees"] = i18n("Fees");
i18nHeaders["interest"] = i18n("Interest");
i18nHeaders["payment"] = i18n("Payment");
i18nHeaders["balance"] = i18n("Balance");
i18nHeaders["type"] = i18n("Type");
i18nHeaders["name"] = i18nc("Account name", "Name");
i18nHeaders["nextduedate"] = i18n("Next Due Date");
i18nHeaders["occurence"] = i18n("Occurrence"); // krazy:exclude=spelling
i18nHeaders["paymenttype"] = i18n("Payment Method");
i18nHeaders["institution"] = i18n("Institution");
i18nHeaders["description"] = i18n("Description");
i18nHeaders["openingdate"] = i18n("Opening Date");
i18nHeaders["currencyname"] = i18n("Currency");
i18nHeaders["balancewarning"] = i18n("Balance Early Warning");
i18nHeaders["maxbalancelimit"] = i18n("Balance Max Limit");
i18nHeaders["creditwarning"] = i18n("Credit Early Warning");
i18nHeaders["maxcreditlimit"] = i18n("Credit Max Limit");
i18nHeaders["tax"] = i18n("Tax");
i18nHeaders["favorite"] = i18n("Preferred");
i18nHeaders["loanamount"] = i18n("Loan Amount");
i18nHeaders["interestrate"] = i18n("Interest Rate");
i18nHeaders["nextinterestchange"] = i18n("Next Interest Change");
i18nHeaders["periodicpayment"] = i18n("Periodic Payment");
i18nHeaders["finalpayment"] = i18n("Final Payment");
i18nHeaders["currentbalance"] = i18n("Current Balance");
+ i18nHeaders["capitalgain"] = i18n("Capital Gain");
// the list of columns which represent money, so we can display them correctly
- QStringList moneyColumns = QString("value,shares,price,latestprice,netinvvalue,buys,sells,cashincome,reinvestincome,startingbal,fees,interest,payment,balance,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,loanamount,periodicpayment,finalpayment,currentbalance,startingbal,endingbal").split(',');
+ QStringList moneyColumns = QString("value,shares,price,latestprice,netinvvalue,buys,sells,cashincome,reinvestincome,startingbal,fees,interest,payment,balance,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,loanamount,periodicpayment,finalpayment,currentbalance,startingbal,endingbal,capitalgain").split(',');
// the list of columns which represent shares, which is like money except the
// transaction currency will not be displayed
QStringList sharesColumns = QString("shares").split(',');
// the list of columns which represent a percentage, so we can display them correctly
QStringList percentColumns = QString("return,returninvestment,interestrate").split(',');
// the list of columns which represent dates, so we can display them correctly
QStringList dateColumns = QString("postdate,entrydate,nextduedate,openingdate,nextinterestchange").split(',');
result += "\n\n";
csv = csv.left(csv.length() - 1);
csv += '\n';
//
// Set up group iterators
//
// There is one active iterator for each level of grouping.
// As we step through the rows
// we update the group iterators each time based on the row data. If
// the group iterator changes and it had a previous value, we print a
// subtotal. Whether or not it had a previous value, we print a group
// header. The group iterator keeps track of a subtotal also.
int depth = 1;
QList groupIteratorList;
QStringList::const_iterator it_grouplevel = groups.constBegin();
while (it_grouplevel != groups.constEnd()) {
groupIteratorList += GroupIterator((*it_grouplevel), m_subtotal, depth++);
++it_grouplevel;
}
//
// Rows
//
bool row_odd = true;
// ***DV***
MyMoneyMoney startingBalance;
for (QList::const_iterator it_row = m_rows.begin();
it_row != m_rows.end();
++it_row) {
// the standard fraction is the fraction of an non-cash account in the base currency
// this could be overridden using the "fraction" element of a row for each row.
// Currently (2008-02-21) this override is not used at all (ipwizard)
int fraction = file->baseCurrency().smallestAccountFraction();
if ((*it_row).find("fraction") != (*it_row).end())
fraction = (*it_row)["fraction"].toInt();
//
// Process Groups
//
// ***DV*** HACK to force a subtotal and header, since this render doesn't
// always detect a group change for different accounts with the same name
// (as occurs with the same stock purchased from different investment accts)
if (it_row != m_rows.begin())
if (((* it_row)["rank"] == "-2") && ((* it_row)["id"] == "A"))
(groupIteratorList.last()).force();
// There's a subtle bug here. If an earlier group gets a new group,
// then we need to force all the downstream groups to get one too.
// Update the group iterators with the current row value
QList::iterator it_group = groupIteratorList.begin();
while (it_group != groupIteratorList.end()) {
(*it_group).update(*it_row);
++it_group;
}
// Do subtotals backwards
if (m_config.isConvertCurrency()) {
it_group = groupIteratorList.end();
if (it_group != groupIteratorList.begin())
--it_group;
while (it_group != groupIteratorList.end()) {
if ((*it_group).isSubtotal()) {
if ((*it_group).depth() == 1)
grandtotal += (*it_group).subtotal();
grandtotal = grandtotal.convert(fraction);
QString subtotal_html = (*it_group).subtotal().formatMoney(fraction);
QString subtotal_csv = (*it_group).subtotal().formatMoney(fraction, false);
// ***DV*** HACK fix the side-effiect from .force() method above
QString oldName = QString((*it_group).oldName()).trimmed();
result +=
"\n";
csv +=
"\"" + i18nc("Total balance", "Total") + " " + oldName + "\",\"" + subtotal_csv + "\"\n";
}
// going beyond begin() is not caught by the iterator
if (it_group == groupIteratorList.begin())
break;
--it_group;
}
}
// And headers forwards
it_group = groupIteratorList.begin();
while (it_group != groupIteratorList.end()) {
if ((*it_group).isNewHeader()) {
row_odd = true;
result += "\n";
csv += "\"" + (*it_group).name() + "\"\n";
}
++it_group;
}
//
// Columns
//
// skip the opening and closing balance row,
// if the balance column is not shown
if ((columns.contains("balance") == 0) && ((*it_row)["rank"] == "-2"))
continue;
bool need_label = true;
QString tlink; // link information to account and transaction
// ***DV***
if ((* it_row)["rank"] == "0") {
row_odd = ! row_odd;
tlink = QString("id=%1&tid=%2")
.arg((* it_row)["accountid"], (* it_row)["id"]);
}
if ((* it_row)["rank"] == "-2")
result += QString("").arg((* it_row)["id"]);
else if ((* it_row)["rank"] == "1")
result += QString("
").arg(row_odd ? "item1" : "item0");
else
result += QString("
").arg(row_odd ? "row-odd " : "row-even");
QStringList::const_iterator it_column = columns.constBegin();
while (it_column != columns.constEnd()) {
QString data = (*it_row)[*it_column];
// ***DV***
if ((* it_row)["rank"] == "1") {
if (* it_column == "value")
data = (* it_row)["split"];
else if (*it_column == "postdate"
|| *it_column == "number"
|| *it_column == "payee"
|| *it_column == "tag"
|| *it_column == "action"
|| *it_column == "shares"
|| *it_column == "price"
|| *it_column == "nextduedate"
|| *it_column == "balance"
|| *it_column == "account"
|| *it_column == "name")
data = "";
}
// ***DV***
if ((* it_row)["rank"] == "-2") {
if (*it_column == "balance") {
data = (* it_row)["balance"];
if ((* it_row)["id"] == "A") // opening balance?
startingBalance = MyMoneyMoney(data);
}
if (need_label) {
if ((*it_column == "payee") ||
(*it_column == "category") ||
(*it_column == "memo")) {
if (!(*it_row)["shares"].isEmpty()) {
data = ((* it_row)["id"] == "A")
? i18n("Initial Market Value")
: i18n("Ending Market Value");
} else {
data = ((* it_row)["id"] == "A")
? i18n("Opening Balance")
: i18n("Closing Balance");
}
need_label = false;
}
}
}
// The 'balance' column is calculated at render-time
// but not printed on split lines
else if (*it_column == "balance" && (* it_row)["rank"] == "0") {
// Take the balance off the deepest group iterator
data = (groupIteratorList.back().currenttotal() + startingBalance).toString();
}
// Figure out how to render the value in this column, depending on
// what its properties are.
//
// TODO: This and the i18n headings are handled
// as a set of parallel vectors. Would be much better to make a single
// vector of a properties class.
QString tlinkBegin, tlinkEnd;
if (!tlink.isEmpty()) {
tlinkBegin = QString("").arg(tlink);
tlinkEnd = QLatin1String("");
}
if (sharesColumns.contains(*it_column)) {
if (data.isEmpty()) {
result += QString(" | ");
csv += "\"\",";
} else {
result += QString("%2%1%3 | ").arg(MyMoneyMoney(data).formatMoney("", 3), tlinkBegin, tlinkEnd);
csv += "\"" + MyMoneyMoney(data).formatMoney("", 3, false) + "\",";
}
} else if (moneyColumns.contains(*it_column)) {
if (data.isEmpty()) {
result += QString(" | ")
.arg((*it_column == "value") ? " class=\"value\"" : "");
csv += "\"\",";
} else if (MyMoneyMoney(data) == MyMoneyMoney::autoCalc) {
result += QString("%3%2%4 | ")
.arg((*it_column == "value") ? " class=\"value\"" : "")
.arg(i18n("Calculated"), tlinkBegin, tlinkEnd);
csv += "\"" + i18n("Calculated") + "\",";
} else if (*it_column == "price") {
result += QString("%2%1%3 | ")
.arg(MyMoneyMoney(data).formatMoney(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())), tlinkBegin, tlinkEnd);
csv += "\"" + (*it_row)["currency"] + " " + MyMoneyMoney(data).formatMoney(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision()), false) + "\",";
} else {
result += QString("%4%2 %3%5 | ")
.arg((*it_column == "value") ? " class=\"value\"" : "")
.arg((*it_row)["currency"])
.arg(MyMoneyMoney(data).formatMoney(fraction))
.arg(tlinkBegin, tlinkEnd);
csv += "\"" + (*it_row)["currency"] + " " + MyMoneyMoney(data).formatMoney(fraction, false) + "\",";
}
} else if (percentColumns.contains(*it_column)) {
data = (MyMoneyMoney(data) * MyMoneyMoney(100, 1)).formatMoney(fraction);
result += QString("%2%1%%3 | ").arg(data, tlinkBegin, tlinkEnd);
csv += data + "%,";
} else if (dateColumns.contains(*it_column)) {
// do this before we possibly change data
csv += "\"" + data + "\",";
// if we have a locale() then use its date formatter
if (!data.isEmpty()) {
QDate qd = QDate::fromString(data, Qt::ISODate);
data = QLocale().toString(qd, QLocale::ShortFormat);
}
result += QString("%2%1%3 | ").arg(data, tlinkBegin, tlinkEnd);
} else {
result += QString("%2%1%3 | ").arg(data, tlinkBegin, tlinkEnd);
csv += "\"" + data + "\",";
}
++it_column;
tlink.clear();
}
result += "
\n";
csv = csv.left(csv.length() - 1); // remove final comma
csv += '\n';
}
//
// Final group totals
//
// Do subtotals backwards
if (m_config.isConvertCurrency()) {
int fraction = file->baseCurrency().smallestAccountFraction();
QList::iterator it_group = groupIteratorList.end();
if (it_group != groupIteratorList.begin())
--it_group;
while (it_group != groupIteratorList.end()) {
(*it_group).update(TableRow());
if ((*it_group).depth() == 1) {
grandtotal += (*it_group).subtotal();
grandtotal = grandtotal.convert(fraction);
}
QString subtotal_html = (*it_group).subtotal().formatMoney(fraction);
QString subtotal_csv = (*it_group).subtotal().formatMoney(fraction, false);
result += "\n";
csv += "\"" + i18nc("Total balance", "Total") + " " + (*it_group).oldName() + "\",\"" + subtotal_csv + "\"\n";
// going beyond begin() is not caught by the iterator
if (it_group == groupIteratorList.begin())
break;
--it_group;
}
//
// Grand total
//
QString grandtotal_html = grandtotal.formatMoney(fraction);
QString grandtotal_csv = grandtotal.formatMoney(fraction, false);
//If we order by Tags don't show the Grand total as we can have multiple tags per transaction
if (m_config.rowType() != MyMoneyReport::eTag) {
result += "\n";
csv += "\"" + i18n("Grand Total") + "\",\"" + grandtotal_csv + "\"\n";
}
}
result += "
\n";
}
QString ListTable::renderBody() const
{
QString html, csv;
render(html, csv);
return html;
}
QString ListTable::renderCSV() const
{
QString html, csv;
render(html, csv);
return csv;
}
void ListTable::dump(const QString& file, const QString& context) const
{
QFile g(file);
g.open(QIODevice::WriteOnly | QIODevice::Text);
if (! context.isEmpty())
QTextStream(&g) << context.arg(renderBody());
else
QTextStream(&g) << renderBody();
g.close();
}
void ListTable::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.
// In case we get called for a non investment only report we quit
if (KMyMoneyGlobalSettings::expertMode() || !m_config.isInvestmentsOnly()) {
return;
}
// get all investment subAccountsList but do not include those with zero balance
// or those which had no transactions during the timeframe of the report
QStringList accountIdList;
QStringList subAccountsList;
MyMoneyFile* file = MyMoneyFile::instance();
// get the report account filter
if (!m_config.accounts(accountIdList)
&& m_config.isInvestmentsOnly()) {
// this will only execute if this is an investment-only report
QList accountList;
file->accountList(accountList);
QList::const_iterator it_ma;
for (it_ma = accountList.constBegin(); it_ma != accountList.constEnd(); ++it_ma) {
if ((*it_ma).accountType() == MyMoneyAccount::Investment) {
accountIdList.append((*it_ma).id());
}
}
}
QStringList::const_iterator it_a;
for (it_a = accountIdList.constBegin(); it_a != accountIdList.constEnd(); ++it_a) {
MyMoneyAccount acc = file->account(*it_a);
if (acc.accountType() == MyMoneyAccount::Investment) {
QStringList::const_iterator it_b;
for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) {
if (!accountIdList.contains(*it_b)) {
subAccountsList.append(*it_b);
}
}
}
}
if (m_config.isInvestmentsOnly()
&& !m_config.isIncludingUnusedAccounts()) {
// if the balance is not zero at the end, include the subaccount
QStringList::iterator it_balance;
for (it_balance = subAccountsList.begin(); it_balance != subAccountsList.end();) {
if (!file->balance((*it_balance), m_config.toDate()).isZero()) {
m_config.addAccount((*it_balance));
it_balance = subAccountsList.erase((it_balance));
} else {
++it_balance;
}
}
// if there are transactions for that subaccount, include them
MyMoneyTransactionFilter filter;
filter.setDateFilter(m_config.fromDate(), m_config.toDate());
filter.addAccount(subAccountsList);
filter.setReportAllSplits(false);
QList transactions = file->transactionList(filter);
QList::const_iterator it_t = transactions.constBegin();
//Check each split for a matching account
for (; it_t != transactions.constEnd(); ++it_t) {
const QList& splits = (*it_t).splits();
QList::const_iterator it_s = splits.begin();
for (; it_s != splits.end(); ++it_s) {
const QString& accountId = (*it_s).accountId();
if (!(*it_s).shares().isZero()
&& subAccountsList.contains(accountId)) {
subAccountsList.removeOne(accountId);
m_config.addAccount(accountId);
}
}
}
} else {
// if not an investment-only report or explicitly including unused accounts
// add all investment subaccounts
m_config.addAccount(subAccountsList);
}
}
}
diff --git a/kmymoney/reports/querytable.cpp b/kmymoney/reports/querytable.cpp
index b02351e2e..fbb1c76d8 100644
--- a/kmymoney/reports/querytable.cpp
+++ b/kmymoney/reports/querytable.cpp
@@ -1,1516 +1,1628 @@
/***************************************************************************
querytable.cpp
-------------------
begin : Fri Jul 23 2004
copyright : (C) 2004-2005 by Ace Jones
(C) 2007 Sascha Pfau
***************************************************************************/
/****************************************************************************
Contains code from the func_xirr and related methods of financial.cpp
- KOffice 1.6 by Sascha Pfau. Sascha agreed to relicense those methods under
GPLv2 or later.
*****************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "querytable.h"
#include
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
#include
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "mymoneytransaction.h"
#include "mymoneyreport.h"
#include "mymoneyexception.h"
#include "kmymoneyutils.h"
#include "reportaccount.h"
#include "reportdebug.h"
#include "kmymoneyglobalsettings.h"
namespace reports
{
// ****************************************************************************
//
// CashFlowListItem implementation
//
// Cash flow analysis tools for investment reports
//
// ****************************************************************************
QDate CashFlowListItem::m_sToday = QDate::currentDate();
MyMoneyMoney CashFlowListItem::NPV(double _rate) const
{
double T = static_cast(m_sToday.daysTo(m_date)) / 365.0;
MyMoneyMoney result(m_value.toDouble() / pow(1 + _rate, T), 100);
//qDebug() << "CashFlowListItem::NPV( " << _rate << " ) == " << result;
return result;
}
// ****************************************************************************
//
// CashFlowList implementation
//
// Cash flow analysis tools for investment reports
//
// ****************************************************************************
CashFlowListItem CashFlowList::mostRecent() const
{
CashFlowList dupe(*this);
qSort(dupe);
//qDebug() << " CashFlowList::mostRecent() == " << dupe.back().date().toString(Qt::ISODate);
return dupe.back();
}
MyMoneyMoney CashFlowList::NPV(double _rate) const
{
MyMoneyMoney result;
const_iterator it_cash = begin();
while (it_cash != end()) {
result += (*it_cash).NPV(_rate);
++it_cash;
}
//qDebug() << "CashFlowList::NPV( " << _rate << " ) == " << result << "------------------------" << endl;
return result;
}
double CashFlowList::calculateXIRR() const
{
double resultRate = 0.00001;
double resultZero = 0.00000;
//if ( args.count() > 2 )
// resultRate = calc->conv()->asFloat ( args[2] ).asFloat();
// check pairs and count >= 2 and guess > -1.0
//if ( args[0].count() != args[1].count() || args[1].count() < 2 || resultRate <= -1.0 )
// return Value::errorVALUE();
// define max epsilon
static const double maxEpsilon = 1e-5;
// max number of iterations
static const int maxIter = 50;
// Newton's method - try to find a res, with a accuracy of maxEpsilon
double rateEpsilon, newRate, resultValue;
int i = 0;
bool contLoop;
do {
resultValue = xirrResult(resultRate);
double resultDerive = xirrResultDerive(resultRate);
//check what happens if xirrResultDerive is zero
//Don't know if it is correct to dismiss the result
if (resultDerive != 0) {
newRate = resultRate - resultValue / resultDerive;
} else {
newRate = resultRate - resultValue;
}
rateEpsilon = fabs(newRate - resultRate);
resultRate = newRate;
contLoop = (rateEpsilon > maxEpsilon) && (fabs(resultValue) > maxEpsilon);
} while (contLoop && (++i < maxIter));
if (contLoop)
return resultZero;
return resultRate;
}
double CashFlowList::xirrResult(double& rate) const
{
QDate date;
double r = rate + 1.0;
double res = 0.00000;//back().value().toDouble();
QList::const_iterator list_it = begin();
while (list_it != end()) {
double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0;
MyMoneyMoney val = (* list_it).value();
if (e_i < 0) {
res += val.toDouble() * pow(r, -e_i);
} else {
res += val.toDouble() / pow(r, e_i);
}
++list_it;
}
return res;
}
double CashFlowList::xirrResultDerive(double& rate) const
{
QDate date;
double r = rate + 1.0;
double res = 0.00000;
QList::const_iterator list_it = begin();
while (list_it != end()) {
double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0;
MyMoneyMoney val = (* list_it).value();
res -= e_i * val.toDouble() / pow(r, e_i + 1.0);
++list_it;
}
return res;
}
double CashFlowList::IRR() const
{
double result = 0.0;
// set 'today', which is the most recent of all dates in the list
CashFlowListItem::setToday(mostRecent().date());
result = calculateXIRR();
return result;
}
MyMoneyMoney CashFlowList::total() const
{
MyMoneyMoney result;
const_iterator it_cash = begin();
while (it_cash != end()) {
result += (*it_cash).value();
++it_cash;
}
return result;
}
void CashFlowList::dumpDebug() const
{
const_iterator it_item = begin();
while (it_item != end()) {
qDebug() << (*it_item).date().toString(Qt::ISODate) << " " << (*it_item).value().toString();
++it_item;
}
}
// ****************************************************************************
//
// QueryTable implementation
//
// ****************************************************************************
/**
* TODO
*
* - Collapse 2- & 3- groups when they are identical
* - Way more test cases (especially splits & transfers)
* - Option to collapse splits
* - Option to exclude transfers
*
*/
QueryTable::QueryTable(const MyMoneyReport& _report): ListTable(_report)
{
// separated into its own method to allow debugging (setting breakpoints
// directly in ctors somehow does not work for me (ipwizard))
// TODO: remove the init() method and move the code back to the ctor
init();
}
void QueryTable::init()
{
switch (m_config.rowType()) {
case MyMoneyReport::eAccountByTopAccount:
case MyMoneyReport::eEquityType:
case MyMoneyReport::eAccountType:
case MyMoneyReport::eInstitution:
constructAccountTable();
m_columns = "account";
break;
case MyMoneyReport::eAccount:
constructTransactionTable();
m_columns = "accountid,postdate";
break;
case MyMoneyReport::ePayee:
case MyMoneyReport::eTag:
case MyMoneyReport::eMonth:
case MyMoneyReport::eWeek:
constructTransactionTable();
m_columns = "postdate,account";
break;
case MyMoneyReport::eCashFlow:
constructSplitsTable();
m_columns = "postdate";
break;
default:
constructTransactionTable();
m_columns = "postdate";
}
// Sort the data to match the report definition
m_subtotal = "value";
switch (m_config.rowType()) {
case MyMoneyReport::eCashFlow:
m_group = "categorytype,topcategory,category";
break;
case MyMoneyReport::eCategory:
m_group = "categorytype,topcategory,category";
break;
case MyMoneyReport::eTopCategory:
m_group = "categorytype,topcategory";
break;
case MyMoneyReport::eTopAccount:
m_group = "topaccount,account";
break;
case MyMoneyReport::eAccount:
m_group = "account";
break;
case MyMoneyReport::eAccountReconcile:
m_group = "account,reconcileflag";
break;
case MyMoneyReport::ePayee:
m_group = "payee";
break;
case MyMoneyReport::eTag:
m_group = "tag";
break;
case MyMoneyReport::eMonth:
m_group = "month";
break;
case MyMoneyReport::eWeek:
m_group = "week";
break;
case MyMoneyReport::eAccountByTopAccount:
m_group = "topaccount";
break;
case MyMoneyReport::eEquityType:
m_group = "equitytype";
break;
case MyMoneyReport::eAccountType:
m_group = "type";
break;
case MyMoneyReport::eInstitution:
m_group = "institution,topaccount";
break;
default:
throw MYMONEYEXCEPTION("QueryTable::QueryTable(): unhandled row type");
}
QString sort = m_group + ',' + m_columns + ",id,rank";
switch (m_config.rowType()) {
case MyMoneyReport::eAccountByTopAccount:
case MyMoneyReport::eEquityType:
case MyMoneyReport::eAccountType:
case MyMoneyReport::eInstitution:
m_columns = "account";
break;
default:
m_columns = "postdate";
}
unsigned qc = m_config.queryColumns();
if (qc & MyMoneyReport::eQCnumber)
m_columns += ",number";
if (qc & MyMoneyReport::eQCpayee)
m_columns += ",payee";
if (qc & MyMoneyReport::eQCtag)
m_columns += ",tag";
if (qc & MyMoneyReport::eQCcategory)
m_columns += ",category";
if (qc & MyMoneyReport::eQCaccount)
m_columns += ",account";
if (qc & MyMoneyReport::eQCreconciled)
m_columns += ",reconcileflag";
if (qc & MyMoneyReport::eQCmemo)
m_columns += ",memo";
if (qc & MyMoneyReport::eQCaction)
m_columns += ",action";
if (qc & MyMoneyReport::eQCshares)
m_columns += ",shares";
if (qc & MyMoneyReport::eQCprice)
m_columns += ",price";
if (qc & MyMoneyReport::eQCperformance) {
m_columns += ",startingbal,buys,sells,reinvestincome,cashincome,return,returninvestment";
m_subtotal = "endingbal";
}
+ if (qc & MyMoneyReport::eQCcapitalgain) {
+ m_columns += ",buys,sells";
+ m_subtotal = "capitalgain";
+ }
if (qc & MyMoneyReport::eQCloan) {
m_columns += ",payment,interest,fees";
m_postcolumns = "balance";
}
if (qc & MyMoneyReport::eQCbalance)
m_postcolumns = "balance";
TableRow::setSortCriteria(sort);
qSort(m_rows);
}
void QueryTable::constructTransactionTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
MyMoneyReport report(m_config);
report.setReportAllSplits(false);
report.setConsiderCategory(true);
bool use_transfers;
bool use_summary;
bool hide_details;
bool tag_special_case = false;
switch (m_config.rowType()) {
case MyMoneyReport::eCategory:
case MyMoneyReport::eTopCategory:
use_summary = false;
use_transfers = false;
hide_details = false;
break;
case MyMoneyReport::ePayee:
use_summary = false;
use_transfers = false;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
break;
case MyMoneyReport::eTag:
use_summary = false;
use_transfers = false;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
tag_special_case = true;
break;
default:
use_summary = true;
use_transfers = true;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
break;
}
// support for opening and closing balances
QMap accts;
//get all transactions for this report
QList transactions = file->transactionList(report);
for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) {
TableRow qA, qS;
QDate pd;
QList tagIdListCache;
qA["id"] = qS["id"] = (* it_transaction).id();
qA["entrydate"] = qS["entrydate"] = (* it_transaction).entryDate().toString(Qt::ISODate);
qA["postdate"] = qS["postdate"] = (* it_transaction).postDate().toString(Qt::ISODate);
qA["commodity"] = qS["commodity"] = (* it_transaction).commodity();
pd = (* it_transaction).postDate();
qA["month"] = qS["month"] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate));
qA["week"] = qS["week"] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate));
qA["currency"] = qS["currency"] = "";
if ((* it_transaction).commodity() != file->baseCurrency().id()) {
if (!report.isConvertCurrency()) {
qA["currency"] = qS["currency"] = (*it_transaction).commodity();
}
}
// to handle splits, we decide on which account to base the split
// (a reference point or point of view so to speak). here we take the
// first account that is a stock account or loan account (or the first account
// that is not an income or expense account if there is no stock or loan account)
// to be the account (qA) that will have the sub-item "split" entries. we add
// one transaction entry (qS) for each subsequent entry in the split.
const QList& splits = (*it_transaction).splits();
QList::const_iterator myBegin, it_split;
for (it_split = splits.begin(), myBegin = splits.end(); it_split != splits.end(); ++it_split) {
ReportAccount splitAcc = (* it_split).accountId();
// always put split with a "stock" account if it exists
if (splitAcc.isInvest())
break;
// prefer to put splits with a "loan" account if it exists
if (splitAcc.isLoan())
myBegin = it_split;
if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) {
myBegin = it_split;
}
}
// select our "reference" split
if (it_split == splits.end()) {
it_split = myBegin;
} else {
myBegin = it_split;
}
// skip this transaction if we didn't find a valid base account - see the above description
// for the base account's description - if we don't find it avoid a crash by skipping the transaction
if (myBegin == splits.end())
continue;
// if the split is still unknown, use the first one. I have seen this
// happen with a transaction that has only a single split referencing an income or expense
// account and has an amount and value of 0. Such a transaction will fall through
// the above logic and leave 'it_split' pointing to splits.end() which causes the remainder
// of this to end in an infinite loop.
if (it_split == splits.end()) {
it_split = splits.begin();
}
// for "loan" reports, the loan transaction gets special treatment.
// the splits of a loan transaction are placed on one line in the
// reference (loan) account (qA). however, we process the matching
// split entries (qS) normally.
bool loan_special_case = false;
if (m_config.queryColumns() & MyMoneyReport::eQCloan) {
ReportAccount splitAcc = (*it_split).accountId();
loan_special_case = splitAcc.isLoan();
}
bool include_me = true;
bool transaction_text = false; //indicates whether a text should be considered as a match for the transaction or for a split only
QString a_fullname = "";
QString a_memo = "";
int pass = 1;
QString myBeginCurrency = (file->account((*myBegin).accountId())).currencyId(); //currency of the main split
do {
MyMoneyMoney xr;
ReportAccount splitAcc = (* it_split).accountId();
//get fraction for account
int fraction = splitAcc.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = splitAcc.institutionId();
QString payee = (*it_split).payeeId();
const QList tagIdList = (*it_split).tagIdList();
//convert to base currency
if (m_config.isConvertCurrency()) {
xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate()) * splitAcc.baseCurrencyPrice((*it_transaction).postDate())).reduce();
} else {
xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate())).reduce();
}
if (splitAcc.isInvest()) {
// use the institution of the parent for stock accounts
institution = splitAcc.parent().institutionId();
MyMoneyMoney shares = (*it_split).shares();
qA["action"] = (*it_split).action();
qA["shares"] = shares.isZero() ? "" : (*it_split).shares().toString();
qA["price"] = shares.isZero() ? "" : xr.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && (*it_split).shares().isNegative())
qA["action"] = "Sell";
qA["investaccount"] = splitAcc.parent().name();
}
if (it_split == myBegin) {
include_me = m_config.includes(splitAcc);
a_fullname = splitAcc.fullName();
a_memo = (*it_split).memo();
transaction_text = m_config.match(&(*it_split));
qA["price"] = xr.toString();
qA["account"] = splitAcc.name();
qA["accountid"] = splitAcc.id();
qA["topaccount"] = splitAcc.topParentName();
qA["institution"] = institution.isEmpty()
? i18n("No Institution")
: file->institution(institution).name();
qA["payee"] = payee.isEmpty()
? i18n("[Empty Payee]")
: file->payee(payee).name().simplified();
if (tag_special_case) {
tagIdListCache = tagIdList;
} else {
QString delimiter = "";
for (int i = 0; i < tagIdList.size(); i++) {
qA["tag"] += delimiter + file->tag(tagIdList[i]).name().simplified();
delimiter = ", ";
}
}
qA["reconciledate"] = (*it_split).reconcileDate().toString(Qt::ISODate);
qA["reconcileflag"] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true);
qA["number"] = (*it_split).number();
qA["memo"] = a_memo;
qA["value"] = (((*it_split).shares()) * xr).convert(fraction).toString();
qS["reconciledate"] = qA["reconciledate"];
qS["reconcileflag"] = qA["reconcileflag"];
qS["number"] = qA["number"];
qS["topcategory"] = splitAcc.topParentName();
qS["categorytype"] = i18n("Transfer");
// only include the configured accounts
if (include_me) {
if (loan_special_case) {
// put the principal amount in the "value" column and convert to lowest fraction
qA["value"] = ((-(*it_split).shares()) * xr).convert(fraction).toString();
qA["rank"] = '0';
qA["split"] = "";
} else {
if ((splits.count() > 2) && use_summary) {
// add the "summarized" split transaction
// this is the sub-total of the split detail
// convert to lowest fraction
qA["value"] = ((*it_split).shares() * xr).convert(fraction).toString();
qA["rank"] = '0';
qA["category"] = i18n("[Split Transaction]");
qA["topcategory"] = i18nc("Split transaction", "Split");
qA["categorytype"] = i18nc("Split transaction", "Split");
m_rows += qA;
}
}
// track accts that will need opening and closing balances
//FIXME in some cases it will show the opening and closing
//balances but no transactions if the splits are all filtered out -- asoliverez
accts.insert(splitAcc.id(), splitAcc);
}
} else {
if (include_me) {
if (loan_special_case) {
MyMoneyMoney value = ((-(* it_split).shares()) * xr).convert(fraction);
if ((*it_split).action() == MyMoneySplit::ActionAmortization) {
// put the payment in the "payment" column and convert to lowest fraction
qA["payment"] = value.toString();
} else if ((*it_split).action() == MyMoneySplit::ActionInterest) {
// put the interest in the "interest" column and convert to lowest fraction
qA["interest"] = value.toString();
} else if (splits.count() > 2) {
// [dv: This comment carried from the original code. I am
// not exactly clear on what it means or why we do this.]
// Put the initial pay-in nowhere (that is, ignore it). This
// is dangerous, though. The only way I can tell the initial
// pay-in apart from fees is if there are only 2 splits in
// the transaction. I wish there was a better way.
} else {
// accumulate everything else in the "fees" column
MyMoneyMoney n0 = MyMoneyMoney(qA["fees"]);
qA["fees"] = (n0 + value).toString();
}
// we don't add qA here for a loan transaction. we'll add one
// qA afer all of the split components have been processed.
// (see below)
}
//--- special case to hide split transaction details
else if (hide_details && (splits.count() > 2)) {
// essentially, don't add any qA entries
}
//--- default case includes all transaction details
else {
//this is when the splits are going to be shown as children of the main split
if ((splits.count() > 2) && use_summary) {
qA["value"] = "";
//convert to lowest fraction
qA["split"] = ((-(*it_split).shares()) * xr).convert(fraction).toString();
qA["rank"] = '1';
} else {
//this applies when the transaction has only 2 splits, or each split is going to be
//shown separately, eg. transactions by category
qA["split"] = "";
// multiply by currency and convert to lowest fraction
// but only for income and expense
// transfers are dealt with somewhere else below
if (splitAcc.isIncomeExpense()) {
// if the currency of the split is different from the currency of the main split, then convert to the currency of the main split
MyMoneyMoney ieXr(xr);
if (!m_config.isConvertCurrency() && splitAcc.currency().id() != myBeginCurrency) {
ieXr = (xr * splitAcc.foreignCurrencyPrice(myBeginCurrency, (*it_transaction).postDate())).reduce();
}
qA["value"] = ((-(*it_split).shares()) * ieXr).convert(fraction).toString();
}
qA["rank"] = '0';
}
qA ["memo"] = (*it_split).memo();
// if different from base currency and not converting
// show the currency of the split
if (splitAcc.currencyId() != file->baseCurrency().id()) {
if (!report.isConvertCurrency()) {
qS["currency"] = splitAcc.currencyId();
}
} else {
qS["currency"] = "";
}
if (! splitAcc.isIncomeExpense()) {
qA["category"] = ((*it_split).shares().isNegative()) ?
i18n("Transfer from %1", splitAcc.fullName())
: i18n("Transfer to %1", splitAcc.fullName());
qA["topcategory"] = splitAcc.topParentName();
qA["categorytype"] = i18n("Transfer");
} else {
qA ["category"] = splitAcc.fullName();
qA ["topcategory"] = splitAcc.topParentName();
qA ["categorytype"] = KMyMoneyUtils::accountTypeToString(splitAcc.accountGroup());
}
if (use_transfers || (splitAcc.isIncomeExpense() && m_config.includes(splitAcc))) {
//if it matches the text of the main split of the transaction or
//it matches this particular split, include it
//otherwise, skip it
//if the filter is "does not contain" exclude the split if it does not match
//even it matches the whole split
if ((m_config.isInvertingText() &&
m_config.match(&(*it_split)))
|| (!m_config.isInvertingText()
&& (transaction_text
|| m_config.match(&(*it_split))))) {
if (tag_special_case) {
if (!tagIdListCache.size())
qA["tag"] = i18n("[No Tag]");
else
for (int i = 0; i < tagIdListCache.size(); i++) {
qA["tag"] = file->tag(tagIdListCache[i]).name().simplified();
m_rows += qA;
}
} else {
m_rows += qA;
}
}
}
}
}
if (m_config.includes(splitAcc) && use_transfers) {
if (! splitAcc.isIncomeExpense()) {
//multiply by currency and convert to lowest fraction
qS["value"] = ((*it_split).shares() * xr).convert(fraction).toString();
qS["rank"] = '0';
qS["account"] = splitAcc.name();
qS["accountid"] = splitAcc.id();
qS["topaccount"] = splitAcc.topParentName();
qS["category"] = ((*it_split).shares().isNegative())
? i18n("Transfer to %1", a_fullname)
: i18n("Transfer from %1", a_fullname);
qS["institution"] = institution.isEmpty()
? i18n("No Institution")
: file->institution(institution).name();
qS["memo"] = (*it_split).memo().isEmpty()
? a_memo
: (*it_split).memo();
//FIXME-ALEX When is used this? I can't find in which condition we arrive here... maybe this code is useless?
QString delimiter = "";
for (int i = 0; i < tagIdList.size(); i++) {
qA["tag"] += delimiter + file->tag(tagIdList[i]).name().simplified();
delimiter = '+';
}
qS["payee"] = payee.isEmpty()
? qA["payee"]
: file->payee(payee).name().simplified();
//check the specific split against the filter for text and amount
//TODO this should be done at the engine, but I have no clear idea how -- asoliverez
//if the filter is "does not contain" exclude the split if it does not match
//even it matches the whole split
if ((m_config.isInvertingText() &&
m_config.match(&(*it_split)))
|| (!m_config.isInvertingText()
&& (transaction_text
|| m_config.match(&(*it_split))))) {
m_rows += qS;
// track accts that will need opening and closing balances
accts.insert(splitAcc.id(), splitAcc);
}
}
}
}
++it_split;
// look for wrap-around
if (it_split == splits.end())
it_split = splits.begin();
// but terminate if this transaction has only a single split
if (splits.count() < 2)
break;
//check if there have been more passes than there are splits
//this is to prevent infinite loops in cases of data inconsistency -- asoliverez
++pass;
if (pass > splits.count())
break;
} while (it_split != myBegin);
if (loan_special_case) {
m_rows += qA;
}
}
// now run through our accts list and add opening and closing balances
switch (m_config.rowType()) {
case MyMoneyReport::eAccount:
case MyMoneyReport::eTopAccount:
break;
// case MyMoneyReport::eCategory:
// case MyMoneyReport::eTopCategory:
// case MyMoneyReport::ePayee:
// case MyMoneyReport::eMonth:
// case MyMoneyReport::eWeek:
default:
return;
}
QDate startDate, endDate;
report.validDateRange(startDate, endDate);
QString strStartDate = startDate.toString(Qt::ISODate);
QString strEndDate = endDate.toString(Qt::ISODate);
startDate = startDate.addDays(-1);
QMap::const_iterator it_account, accts_end;
for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) {
TableRow qA;
ReportAccount account = (* it_account);
//get fraction for account
int fraction = account.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = account.institutionId();
// use the institution of the parent for stock accounts
if (account.isInvest())
institution = account.parent().institutionId();
MyMoneyMoney startBalance, endBalance, startPrice, endPrice;
MyMoneyMoney startShares, endShares;
//get price and convert currency if necessary
if (m_config.isConvertCurrency()) {
startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce();
endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce();
} else {
startPrice = account.deepCurrencyPrice(startDate).reduce();
endPrice = account.deepCurrencyPrice(endDate).reduce();
}
startShares = file->balance(account.id(), startDate);
endShares = file->balance(account.id(), endDate);
//get starting and ending balances
startBalance = startShares * startPrice;
endBalance = endShares * endPrice;
//starting balance
// don't show currency if we're converting or if it's not foreign
qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id();
qA["accountid"] = account.id();
qA["account"] = account.name();
qA["topaccount"] = account.topParentName();
qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name();
qA["rank"] = "-2";
qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
if (account.isInvest()) {
qA["shares"] = startShares.toString();
}
qA["postdate"] = strStartDate;
qA["balance"] = startBalance.convert(fraction).toString();
qA["value"].clear();
qA["id"] = 'A';
m_rows += qA;
//ending balance
qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
if (account.isInvest()) {
qA["shares"] = endShares.toString();
}
qA["postdate"] = strEndDate;
qA["balance"] = endBalance.toString();
qA["id"] = 'Z';
m_rows += qA;
}
}
void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result) const
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity security;
//get fraction depending on type of account
int fraction = account.currency().smallestAccountFraction();
//
// Calculate performance
//
// The following columns are created:
// Account, Value on , Buys, Sells, Income, Value on , Return%
MyMoneyReport report = m_config;
QDate startingDate;
QDate endingDate;
MyMoneyMoney price;
report.validDateRange(startingDate, endingDate);
startingDate = startingDate.addDays(-1);
//calculate starting balance
if (m_config.isConvertCurrency()) {
price = account.deepCurrencyPrice(startingDate) * account.baseCurrencyPrice(startingDate);
} else {
price = account.deepCurrencyPrice(startingDate);
}
//work around if there is no price for the starting balance
if (!(file->balance(account.id(), startingDate)).isZero()
&& account.deepCurrencyPrice(startingDate) == MyMoneyMoney::ONE) {
MyMoneyTransactionFilter filter;
//get the transactions for the time before the report
filter.setDateFilter(QDate(), startingDate);
filter.addAccount(account.id());
filter.setReportAllSplits(true);
QList startTransactions = file->transactionList(filter);
if (startTransactions.size() > 0) {
//get the last transaction
MyMoneyTransaction startTrans = startTransactions.back();
MyMoneySplit s = startTrans.splitByAccount(account.id());
//get the price from the split of that account
price = s.price();
if (m_config.isConvertCurrency())
price = price * account.baseCurrencyPrice(startingDate);
}
}
if (m_config.isConvertCurrency()) {
price = account.deepCurrencyPrice(startingDate) * account.baseCurrencyPrice(startingDate);
} else {
price = account.deepCurrencyPrice(startingDate);
}
MyMoneyMoney startingBal = file->balance(account.id(), startingDate) * price;
//convert to lowest fraction
startingBal = startingBal.convert(fraction);
//calculate ending balance
if (m_config.isConvertCurrency()) {
price = account.deepCurrencyPrice(endingDate) * account.baseCurrencyPrice(endingDate);
} else {
price = account.deepCurrencyPrice(endingDate);
}
MyMoneyMoney endingBal = file->balance((account).id(), endingDate) * price;
//convert to lowest fraction
endingBal = endingBal.convert(fraction);
CashFlowList buys;
CashFlowList sells;
CashFlowList reinvestincome;
CashFlowList cashincome;
report.setReportAllSplits(false);
report.setConsiderCategory(true);
report.clearAccountFilter();
report.addAccount(account.id());
QList transactions = file->transactionList(report);
QList::const_iterator it_transaction = transactions.constBegin();
while (it_transaction != transactions.constEnd()) {
// s is the split for the stock account
MyMoneySplit s = (*it_transaction).splitByAccount(account.id());
MyMoneySplit assetAccountSplit;
QList feeSplits;
QList interestSplits;
MyMoneySecurity currency;
MyMoneySplit::investTransactionTypeE transactionType;
KMyMoneyUtils::dissectTransaction((*it_transaction), s, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
//get price for the day of the transaction if we have to calculate base currency
//we are using the value of the split which is in deep currency
if (m_config.isConvertCurrency()) {
price = account.baseCurrencyPrice((*it_transaction).postDate()); //we only need base currency because the value is in deep currency
} else {
price = MyMoneyMoney::ONE;
}
MyMoneyMoney value = assetAccountSplit.value() * price;
if (transactionType == MyMoneySplit::BuyShares)
buys += CashFlowListItem((*it_transaction).postDate(), value);
else if (transactionType == MyMoneySplit::SellShares)
sells += CashFlowListItem((*it_transaction).postDate(), value);
else if (transactionType == MyMoneySplit::ReinvestDividend)
reinvestincome += CashFlowListItem((*it_transaction).postDate(), value);
else if (transactionType == MyMoneySplit::Dividend || transactionType == MyMoneySplit::Yield)
cashincome += CashFlowListItem((*it_transaction).postDate(), value);
++it_transaction;
}
// Note that reinvested dividends are not included , because these do not
// represent a cash flow event.
CashFlowList all;
all += buys;
all += sells;
all += cashincome;
all += CashFlowListItem(startingDate, -startingBal);
all += CashFlowListItem(endingDate, endingBal);
MyMoneyMoney returnInvestment;
MyMoneyMoney buysTotal = buys.total();
MyMoneyMoney sellsTotal = sells.total();
MyMoneyMoney cashincomeTotal = cashincome.total();
if (!buysTotal.isZero()) {
returnInvestment = (sellsTotal + buysTotal + cashincomeTotal + endingBal - startingBal) / (startingBal - buysTotal);
returnInvestment = returnInvestment.convert(10000);
} else
returnInvestment = MyMoneyMoney(); // if no investment then no return on investment
try {
double irr = all.IRR();
#ifdef Q_CC_MSVC
MyMoneyMoney annualReturn = MyMoneyMoney(_isnan(irr) ? 0 : irr, 10000);
#else
MyMoneyMoney annualReturn = MyMoneyMoney(std::isnan(irr) ? 0 : irr, 10000);
#endif
result["return"] = annualReturn.toString();
result["returninvestment"] = returnInvestment.toString();
} catch (QString e) {
qDebug() << e;
}
result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType());
result["buys"] = buys.total().toString();
result["sells"] = sells.total().toString();
result["cashincome"] = cashincome.total().toString();
result["reinvestincome"] = reinvestincome.total().toString();
result["startingbal"] = startingBal.toString();
result["endingbal"] = endingBal.toString();
}
+void QueryTable::constructCapitalGainRow(const ReportAccount& account, TableRow& result) const
+{
+ MyMoneyFile* file = MyMoneyFile::instance();
+ MyMoneySecurity security;
+ MyMoneyMoney price;
+ MyMoneyMoney sellValue;
+ MyMoneyMoney buyValue;
+ MyMoneyMoney sellShares;
+ MyMoneyMoney buyShares;
+
+ //
+ // Calculate capital gain
+ //
+
+ // The following columns are created:
+ // Account, Buys, Sells, Capital Gain
+
+ MyMoneyReport report = m_config;
+ QDate startingDate;
+ QDate endingDate;
+ QDate newStartingDate;
+ QDate newEndingDate;
+ report.validDateRange(startingDate, endingDate);
+ newStartingDate = startingDate;
+ newEndingDate = endingDate;
+ MyMoneyMoney endingShares = file->balance(account.id(), endingDate); // get how many shares there are over zero value
+
+ bool reportedDateRange = true; // flag marking sell transactions between startingDate and endingDate
+ report.setReportAllSplits(false);
+ report.setConsiderCategory(true);
+ report.clearAccountFilter();
+ report.addAccount(account.id());
+
+ do {
+ QList transactions = file->transactionList(report);
+ for (QList::const_reverse_iterator it_t = transactions.crbegin(); it_t != transactions.crend(); ++it_t) {
+ MyMoneySplit shareSplit = (*it_t).splitByAccount(account.id());
+ MyMoneySplit assetAccountSplit;
+ QList feeSplits;
+ QList interestSplits;
+ MyMoneySecurity currency;
+ MyMoneySplit::investTransactionTypeE transactionType;
+ KMyMoneyUtils::dissectTransaction((*it_t), shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
+ //get price for the day of the transaction if we have to calculate base currency
+ //we are using the value of the split which is in deep currency
+ if (m_config.isConvertCurrency())
+ price = account.baseCurrencyPrice((*it_t).postDate()); //we only need base currency because the value is in deep currency
+ else
+ price = MyMoneyMoney::ONE;
+
+ MyMoneyMoney value = assetAccountSplit.value() * price;
+ MyMoneyMoney shares = shareSplit.shares();
+
+ if (transactionType == MyMoneySplit::BuyShares) {
+ if (endingShares.isZero()) { // add sold shares
+ if (buyShares + shares > sellShares.abs()) { // add partially sold shares
+ buyValue += (((sellShares.abs() - buyShares)) / shares) * value;
+ buyShares = sellShares.abs();
+ } else { // add wholly sold shares
+ buyValue += value;
+ buyShares += shares;
+ }
+ } else if (endingShares >= shares) { // substract not-sold shares
+ endingShares -= shares;
+ } else { // substract partially not-sold shares
+ buyValue += ((shares - endingShares) / shares) * value;
+ buyShares += (shares - endingShares);
+ endingShares = MyMoneyMoney(0);
+ }
+ } else if (transactionType == MyMoneySplit::SellShares && reportedDateRange) {
+ sellValue += value;
+ sellShares += shares;
+ } else if (transactionType == MyMoneySplit::SplitShares) { // shares variable is denominator of split ratio here
+ sellShares /= shares;
+ buyShares /= shares;
+ } else if (transactionType == MyMoneySplit::AddShares) { // added shares, when sold give 100% capital gain
+ if (endingShares.isZero()) { // add added shares
+ if (buyShares + shares > sellShares.abs()) { // add partially added shares
+ buyShares = sellShares.abs();
+ } else { // add wholly added shares
+ buyShares += shares;
+ }
+ } else if (endingShares >= shares) { // substract not-added shares
+ endingShares -= shares;
+ } else { // substract partially not-added shares
+ buyShares += (shares - endingShares);
+ endingShares = MyMoneyMoney(0);
+ }
+ } else if (transactionType == MyMoneySplit::RemoveShares && reportedDateRange) { // removed shares give no value in return so no capital gain on them
+ sellShares += shares;
+ }
+ }
+ reportedDateRange = false;
+ newEndingDate = newStartingDate;
+ newStartingDate = newStartingDate.addYears(-1);
+ report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier
+ } while (!sellShares.isZero() && account.openingDate() <= newEndingDate && sellShares.abs() > buyShares.abs());
+
+ result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType());
+ result["buys"] = buyValue.toString();
+ result["sells"] = sellValue.toString();
+ result["capitalgain"] = (buyValue + sellValue).toString();
+
+ report.setDateFilter(startingDate, endingDate); // reset data filter for next security
+}
+
void QueryTable::constructAccountTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
QList accounts;
file->accountList(accounts);
QList::const_iterator it_account = accounts.constBegin();
while (it_account != accounts.constEnd()) {
ReportAccount account = *it_account;
//get fraction for account
int fraction = account.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction();
// Note, "Investment" accounts are never included in account rows because
// they don't contain anything by themselves. In reports, they are only
// useful as a "topaccount" aggregator of stock accounts
if (account.isAssetLiability() && m_config.includes(account) && account.accountType() != MyMoneyAccount::Investment) {
TableRow qaccountrow;
// help for sort and render functions
qaccountrow["rank"] = '0';
//
// Handle currency conversion
//
MyMoneyMoney displayprice(1, 1);
if (m_config.isConvertCurrency()) {
// display currency is base currency, so set the price
if (account.isForeignCurrency())
displayprice = account.baseCurrencyPrice(m_config.toDate()).reduce();
} else {
// display currency is the account's deep currency. display this fact in the report
qaccountrow["currency"] = account.currency().id();
}
qaccountrow["account"] = account.name();
qaccountrow["accountid"] = account.id();
qaccountrow["topaccount"] = account.topParentName();
MyMoneyMoney shares = file->balance(account.id(), m_config.toDate());
qaccountrow["shares"] = shares.toString();
MyMoneyMoney netprice = account.deepCurrencyPrice(m_config.toDate()).reduce() * displayprice;
qaccountrow["price"] = (netprice.reduce()).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
qaccountrow["value"] = (netprice.reduce() * shares.reduce()).convert(fraction).toString();
QString iid = (*it_account).institutionId();
// If an account does not have an institution, get it from the top-parent.
if (iid.isEmpty() && ! account.isTopLevel()) {
ReportAccount topaccount = account.topParent();
iid = topaccount.institutionId();
}
if (iid.isEmpty())
qaccountrow["institution"] = i18nc("No institution", "None");
else
qaccountrow["institution"] = file->institution(iid).name();
qaccountrow["type"] = KMyMoneyUtils::accountTypeToString((*it_account).accountType());
if (m_config.queryColumns() == MyMoneyReport::eQCperformance) {
constructPerformanceRow(account, qaccountrow);
+ } else if (m_config.queryColumns() == MyMoneyReport::eQCcapitalgain) {
+ constructCapitalGainRow(account, qaccountrow);
} else
qaccountrow["equitytype"].clear();
// don't add the account if it is closed. In fact, the business logic
// should prevent that an account can be closed with a balance not equal
// to zero, but we never know.
if (!(shares.isZero() && account.isClosed()))
m_rows += qaccountrow;
}
++it_account;
}
}
void QueryTable::constructSplitsTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
MyMoneyReport report(m_config);
report.setReportAllSplits(false);
report.setConsiderCategory(true);
// support for opening and closing balances
QMap accts;
//get all transactions for this report
QList transactions = file->transactionList(report);
for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) {
TableRow qA, qS;
QDate pd;
qA["id"] = qS["id"] = (* it_transaction).id();
qA["entrydate"] = qS["entrydate"] = (* it_transaction).entryDate().toString(Qt::ISODate);
qA["postdate"] = qS["postdate"] = (* it_transaction).postDate().toString(Qt::ISODate);
qA["commodity"] = qS["commodity"] = (* it_transaction).commodity();
pd = (* it_transaction).postDate();
qA["month"] = qS["month"] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate));
qA["week"] = qS["week"] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate));
qA["currency"] = qS["currency"] = "";
if ((* it_transaction).commodity() != file->baseCurrency().id()) {
if (!report.isConvertCurrency()) {
qA["currency"] = qS["currency"] = (*it_transaction).commodity();
}
}
// to handle splits, we decide on which account to base the split
// (a reference point or point of view so to speak). here we take the
// first account that is a stock account or loan account (or the first account
// that is not an income or expense account if there is no stock or loan account)
// to be the account (qA) that will have the sub-item "split" entries. we add
// one transaction entry (qS) for each subsequent entry in the split.
const QList& splits = (*it_transaction).splits();
QList::const_iterator myBegin, it_split;
//S_end = splits.end();
for (it_split = splits.begin(), myBegin = splits.end(); it_split != splits.end(); ++it_split) {
ReportAccount splitAcc = (* it_split).accountId();
// always put split with a "stock" account if it exists
if (splitAcc.isInvest())
break;
// prefer to put splits with a "loan" account if it exists
if (splitAcc.isLoan())
myBegin = it_split;
if ((myBegin == splits.end()) && ! splitAcc.isIncomeExpense()) {
myBegin = it_split;
}
}
// select our "reference" split
if (it_split == splits.end()) {
it_split = myBegin;
} else {
myBegin = it_split;
}
// if the split is still unknown, use the first one. I have seen this
// happen with a transaction that has only a single split referencing an income or expense
// account and has an amount and value of 0. Such a transaction will fall through
// the above logic and leave 'it_split' pointing to splits.end() which causes the remainder
// of this to end in an infinite loop.
if (it_split == splits.end()) {
it_split = splits.begin();
}
// for "loan" reports, the loan transaction gets special treatment.
// the splits of a loan transaction are placed on one line in the
// reference (loan) account (qA). however, we process the matching
// split entries (qS) normally.
bool loan_special_case = false;
if (m_config.queryColumns() & MyMoneyReport::eQCloan) {
ReportAccount splitAcc = (*it_split).accountId();
loan_special_case = splitAcc.isLoan();
}
// There is a slight chance that at this point myBegin is still pointing to splits.end() if the
// transaction only has income and expense splits (which should not happen). In that case, point
// it to the first split
if (myBegin == splits.end()) {
myBegin = splits.begin();
}
//the account of the beginning splits
ReportAccount myBeginAcc = (*myBegin).accountId();
bool include_me = true;
QString a_fullname = "";
QString a_memo = "";
int pass = 1;
do {
MyMoneyMoney xr;
ReportAccount splitAcc = (* it_split).accountId();
//get fraction for account
int fraction = splitAcc.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = splitAcc.institutionId();
QString payee = (*it_split).payeeId();
const QList tagIdList = (*it_split).tagIdList();
if (m_config.isConvertCurrency()) {
xr = (splitAcc.deepCurrencyPrice((*it_transaction).postDate()) * splitAcc.baseCurrencyPrice((*it_transaction).postDate())).reduce();
} else {
xr = splitAcc.deepCurrencyPrice((*it_transaction).postDate()).reduce();
}
// reverse the sign of incomes and expenses to keep consistency in the way it is displayed in other reports
if (splitAcc.isIncomeExpense()) {
xr = -xr;
}
if (splitAcc.isInvest()) {
// use the institution of the parent for stock accounts
institution = splitAcc.parent().institutionId();
MyMoneyMoney shares = (*it_split).shares();
qA["action"] = (*it_split).action();
qA["shares"] = shares.isZero() ? "" : (*it_split).shares().toString();
qA["price"] = shares.isZero() ? "" : xr.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && (*it_split).shares().isNegative())
qA["action"] = "Sell";
qA["investaccount"] = splitAcc.parent().name();
}
include_me = m_config.includes(splitAcc);
a_fullname = splitAcc.fullName();
a_memo = (*it_split).memo();
qA["price"] = xr.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
qA["account"] = splitAcc.name();
qA["accountid"] = splitAcc.id();
qA["topaccount"] = splitAcc.topParentName();
qA["institution"] = institution.isEmpty()
? i18n("No Institution")
: file->institution(institution).name();
//FIXME-ALEX Is this useless? Isn't constructSplitsTable called only for cashflow type report?
QString delimiter = "";
for (int i = 0; i < tagIdList.size(); i++) {
qA["tag"] += delimiter + file->tag(tagIdList[i]).name().simplified();
delimiter = ',';
}
qA["payee"] = payee.isEmpty()
? i18n("[Empty Payee]")
: file->payee(payee).name().simplified();
qA["reconciledate"] = (*it_split).reconcileDate().toString(Qt::ISODate);
qA["reconcileflag"] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true);
qA["number"] = (*it_split).number();
qA["memo"] = a_memo;
qS["reconciledate"] = qA["reconciledate"];
qS["reconcileflag"] = qA["reconcileflag"];
qS["number"] = qA["number"];
qS["topcategory"] = splitAcc.topParentName();
// only include the configured accounts
if (include_me) {
// add the "summarized" split transaction
// this is the sub-total of the split detail
// convert to lowest fraction
qA["value"] = ((*it_split).shares() * xr).convert(fraction).toString();
qA["rank"] = '0';
//fill in account information
if (! splitAcc.isIncomeExpense() && it_split != myBegin) {
qA["account"] = ((*it_split).shares().isNegative()) ?
i18n("Transfer to %1", myBeginAcc.fullName())
: i18n("Transfer from %1", myBeginAcc.fullName());
} else if (it_split == myBegin) {
//handle the main split
if ((splits.count() > 2)) {
//if it is the main split and has multiple splits, note that
qA["account"] = i18n("[Split Transaction]");
} else {
//fill the account name of the second split
QList::const_iterator tempSplit = splits.begin();
//there are supposed to be only 2 splits if we ever get here
if (tempSplit == myBegin && splits.count() > 1)
++tempSplit;
//show the name of the category, or "transfer to/from" if it as an account
ReportAccount tempSplitAcc = (*tempSplit).accountId();
if (! tempSplitAcc.isIncomeExpense()) {
qA["account"] = ((*it_split).shares().isNegative()) ?
i18n("Transfer to %1", tempSplitAcc.fullName())
: i18n("Transfer from %1", tempSplitAcc.fullName());
} else {
qA["account"] = tempSplitAcc.fullName();
}
}
} else {
//in any other case, fill in the account name of the main split
qA["account"] = myBeginAcc.fullName();
}
//category data is always the one of the split
qA ["category"] = splitAcc.fullName();
qA ["topcategory"] = splitAcc.topParentName();
qA ["categorytype"] = KMyMoneyUtils::accountTypeToString(splitAcc.accountGroup());
m_rows += qA;
// track accts that will need opening and closing balances
accts.insert(splitAcc.id(), splitAcc);
}
++it_split;
// look for wrap-around
if (it_split == splits.end())
it_split = splits.begin();
//check if there have been more passes than there are splits
//this is to prevent infinite loops in cases of data inconsistency -- asoliverez
++pass;
if (pass > splits.count())
break;
} while (it_split != myBegin);
if (loan_special_case) {
m_rows += qA;
}
}
// now run through our accts list and add opening and closing balances
switch (m_config.rowType()) {
case MyMoneyReport::eAccount:
case MyMoneyReport::eTopAccount:
break;
// case MyMoneyReport::eCategory:
// case MyMoneyReport::eTopCategory:
// case MyMoneyReport::ePayee:
// case MyMoneyReport::eMonth:
// case MyMoneyReport::eWeek:
default:
return;
}
QDate startDate, endDate;
report.validDateRange(startDate, endDate);
QString strStartDate = startDate.toString(Qt::ISODate);
QString strEndDate = endDate.toString(Qt::ISODate);
startDate = startDate.addDays(-1);
QMap::const_iterator it_account, accts_end;
for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) {
TableRow qA;
ReportAccount account = (* it_account);
//get fraction for account
int fraction = account.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = account.institutionId();
// use the institution of the parent for stock accounts
if (account.isInvest())
institution = account.parent().institutionId();
MyMoneyMoney startBalance, endBalance, startPrice, endPrice;
MyMoneyMoney startShares, endShares;
//get price and convert currency if necessary
if (m_config.isConvertCurrency()) {
startPrice = (account.deepCurrencyPrice(startDate) * account.baseCurrencyPrice(startDate)).reduce();
endPrice = (account.deepCurrencyPrice(endDate) * account.baseCurrencyPrice(endDate)).reduce();
} else {
startPrice = account.deepCurrencyPrice(startDate).reduce();
endPrice = account.deepCurrencyPrice(endDate).reduce();
}
startShares = file->balance(account.id(), startDate);
endShares = file->balance(account.id(), endDate);
//get starting and ending balances
startBalance = startShares * startPrice;
endBalance = endShares * endPrice;
//starting balance
// don't show currency if we're converting or if it's not foreign
qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id();
qA["accountid"] = account.id();
qA["account"] = account.name();
qA["topaccount"] = account.topParentName();
qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name();
qA["rank"] = "-2";
qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
if (account.isInvest()) {
qA["shares"] = startShares.toString();
}
qA["postdate"] = strStartDate;
qA["balance"] = startBalance.convert(fraction).toString();
qA["value"].clear();
qA["id"] = 'A';
m_rows += qA;
//ending balance
qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString();
if (account.isInvest()) {
qA["shares"] = endShares.toString();
}
qA["postdate"] = strEndDate;
qA["balance"] = endBalance.toString();
qA["id"] = 'Z';
m_rows += qA;
}
}
}
diff --git a/kmymoney/reports/querytable.h b/kmymoney/reports/querytable.h
index 7e2bfa1bd..6dacfb18d 100644
--- a/kmymoney/reports/querytable.h
+++ b/kmymoney/reports/querytable.h
@@ -1,157 +1,158 @@
/***************************************************************************
querytable.h
-------------------
begin : Fri Jul 23 2004
copyright : (C) 2004-2005 by Ace Jones
(C) 2007 Sascha Pfau
***************************************************************************/
/****************************************************************************
Contains code from the func_xirr and related methods of financial.cpp
- KOffice 1.6 by Sascha Pfau. Sascha agreed to relicense those methods under
GPLv2 or later.
*****************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QUERYTABLE_H
#define QUERYTABLE_H
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyreport.h"
#include "listtable.h"
namespace reports
{
class ReportAccount;
/**
* Calculates a query of information about the transaction database.
*
* This is a middle-layer class, between the UI and the engine. The
* MyMoneyReport class holds only the CONFIGURATION parameters. This
* class actually does the work of retrieving the data from the engine
* and formatting it for the user.
*
* @author Ace Jones
*
* @short
**/
class QueryTable : public ListTable
{
public:
QueryTable(const MyMoneyReport&);
void init();
protected:
void constructAccountTable();
void constructTransactionTable();
+ void constructCapitalGainRow(const ReportAccount& account, TableRow& result) const;
void constructPerformanceRow(const ReportAccount& account, TableRow& result) const;
void constructSplitsTable();
};
//
// Cash Flow analysis tools for investment reports
//
class CashFlowListItem
{
public:
CashFlowListItem() {}
CashFlowListItem(const QDate& _date, const MyMoneyMoney& _value): m_date(_date), m_value(_value) {}
bool operator<(const CashFlowListItem& _second) const {
return m_date < _second.m_date;
}
bool operator<=(const CashFlowListItem& _second) const {
return m_date <= _second.m_date;
}
bool operator>(const CashFlowListItem& _second) const {
return m_date > _second.m_date;
}
const QDate& date() const {
return m_date;
}
const MyMoneyMoney& value() const {
return m_value;
}
MyMoneyMoney NPV(double _rate) const;
static void setToday(const QDate& _today) {
m_sToday = _today;
}
const QDate& today() const {
return m_sToday;
}
private:
QDate m_date;
MyMoneyMoney m_value;
static QDate m_sToday;
};
class CashFlowList: public QList
{
public:
CashFlowList() {}
MyMoneyMoney NPV(double rate) const;
double IRR() const;
MyMoneyMoney total() const;
void dumpDebug() const;
/**
* Function: XIRR
*
* Compute the internal rate of return for a non-periodic series of cash flows.
*
* XIRR ( Values; Dates; [ Guess = 0.1 ] )
**/
double calculateXIRR() const;
protected:
CashFlowListItem mostRecent() const;
private:
/**
* helper: xirrResult
*
* args[0] = values
* args[1] = dates
**/
double xirrResult(double& rate) const;
/**
*
* helper: xirrResultDerive
*
* args[0] = values
* args[1] = dates
**/
double xirrResultDerive(double& rate) const;
};
}
#endif // QUERYREPORT_H
diff --git a/kmymoney/views/kreportsview.cpp b/kmymoney/views/kreportsview.cpp
index b7cdc013e..8d054550c 100644
--- a/kmymoney/views/kreportsview.cpp
+++ b/kmymoney/views/kreportsview.cpp
@@ -1,1676 +1,1697 @@
/***************************************************************************
kreportsview.cpp - description
-------------------
begin : Sat Mar 27 2004
copyright : (C) 2000-2004 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales
Felix Rodriguez
John C
Thomas Baumgart
Kevin Tambascio
Ace Jones
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "kreportsview.h"
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// Project Includes
#include
#include
#include
#include "querytable.h"
#include "objectinfotable.h"
#include "kreportconfigurationfilterdlg.h"
using namespace reports;
#define VIEW_LEDGER "ledger"
#define VIEW_SCHEDULE "schedule"
#define VIEW_WELCOME "welcome"
#define VIEW_HOME "home"
#define VIEW_REPORTS "reports"
/**
* KReportsView::KReportTab Implementation
*/
KReportsView::KReportTab::KReportTab(QTabWidget* parent, const MyMoneyReport& report):
QWidget(parent),
m_part(new KHTMLPart(this)),
m_chartView(new KReportChartView(this)),
m_control(new kMyMoneyReportControl(this)),
m_layout(new QVBoxLayout(this)),
m_report(report),
m_deleteMe(false),
m_chartEnabled(false),
m_showingChart(false),
m_needReload(true),
m_table(0)
{
m_layout->setSpacing(6);
m_part->setFontScaleFactor(KMyMoneyGlobalSettings::fontSizePercentage());
//set button icons
m_control->buttonChart->setIcon(QIcon::fromTheme("office-chart-line"));
m_control->buttonClose->setIcon(QIcon::fromTheme("document-close"));
m_control->buttonConfigure->setIcon(QIcon::fromTheme("configure"));
m_control->buttonCopy->setIcon(QIcon::fromTheme("edit-copy"));
m_control->buttonDelete->setIcon(QIcon::fromTheme("edit-delete"));
m_control->buttonExport->setIcon(QIcon::fromTheme("document-export"));
m_control->buttonNew->setIcon(QIcon::fromTheme("document-new"));
m_chartView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_chartView->hide();
m_layout->addWidget(m_control);
m_layout->addWidget(m_part->view());
m_layout->addWidget(m_chartView);
parent->addTab(this, QIcon::fromTheme("application-vnd.oasis.opendocument.spreadsheet"), report.name());
parent->setTabEnabled(parent->indexOf(this), true);
// get users character set encoding
m_encoding = QTextCodec::codecForLocale()->name();
}
KReportsView::KReportTab::~KReportTab()
{
delete m_table;
//This is to prevent a crash on exit with KDE 4.3.2
delete m_part;
}
void KReportsView::KReportTab::print()
{
if (m_part && m_part->view())
m_part->view()->print();
}
void KReportsView::KReportTab::copyToClipboard()
{
QMimeData* pMimeData = new QMimeData();
pMimeData->setHtml(m_table->renderHTML(qobject_cast(this),
m_encoding, m_report.name(), true));
QApplication::clipboard()->setMimeData(pMimeData);
}
void KReportsView::KReportTab::saveAs(const QString& filename, bool includeCSS)
{
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
if (QFileInfo(filename).suffix().toLower() == "csv") {
QTextStream(&file) << m_table->renderCSV();
} else {
QString table =
m_table->renderHTML(qobject_cast(this), m_encoding,
m_report.name(), includeCSS);
QTextStream stream(&file);
stream << table;
}
file.close();
}
}
void KReportsView::KReportTab::loadTab()
{
m_needReload = true;
if (isVisible()) {
m_needReload = false;
updateReport();
}
}
void KReportsView::KReportTab::showEvent(QShowEvent * event)
{
if (m_needReload) {
m_needReload = false;
updateReport();
}
QWidget::showEvent(event);
}
void KReportsView::KReportTab::updateReport()
{
// reload the report from the engine. It might have
// been changed by the user
try {
// Don't try to reload default reports from the engine
if (!m_report.id().isEmpty())
m_report = MyMoneyFile::instance()->report(m_report.id());
} catch (const MyMoneyException &) {
}
delete m_table;
m_table = 0;
if (m_report.reportType() == MyMoneyReport::ePivotTable) {
m_table = new PivotTable(m_report);
m_chartEnabled = true;
} else if (m_report.reportType() == MyMoneyReport::eQueryTable) {
m_table = new QueryTable(m_report);
m_chartEnabled = false;
} else if (m_report.reportType() == MyMoneyReport::eInfoTable) {
m_table = new ObjectInfoTable(m_report);
m_chartEnabled = false;
}
m_part->begin();
m_part->write(m_table->renderHTML(qobject_cast(this),
m_encoding, m_report.name()));
m_part->end();
m_table->drawChart(*m_chartView);
m_control->buttonChart->setEnabled(m_chartEnabled);
if (m_report.isChartByDefault() && !m_showingChart)
toggleChart();
}
void KReportsView::KReportTab::toggleChart()
{
// for now it will just SHOW the chart. In the future it actually has to toggle it.
if (m_showingChart) {
m_part->view()->show();
m_chartView->hide();
m_control->buttonChart->setText(i18n("Chart"));
m_control->buttonChart->setToolTip(i18n("Show the chart version of this report"));
m_control->buttonChart->setIcon(QIcon::fromTheme("office-chart-line"));
} else {
m_part->view()->hide();
m_chartView->show();
m_control->buttonChart->setText(i18n("Report"));
m_control->buttonChart->setToolTip(i18n("Show the report version of this chart"));
m_control->buttonChart->setIcon(QIcon::fromTheme("view-financial-list"));
}
m_showingChart = ! m_showingChart;
}
/**
* KReportsView Implementation
*/
KReportsView::KReportsView(QWidget *parent, const char *name) :
KMyMoneyViewBase(parent, name, i18n("Reports")),
m_needReload(false),
m_reportListView(0)
{
// build reports toc
setColumnsAlreadyAdjusted(false);
m_reportTabWidget = new QTabWidget(this);
addWidget(m_reportTabWidget);
m_reportTabWidget->setTabsClosable(true);
m_listTab = new QWidget(m_reportTabWidget);
m_listTabLayout = new QVBoxLayout(m_listTab);
m_listTabLayout->setSpacing(6);
m_tocTreeWidget = new QTreeWidget(m_listTab);
// report-group items have only 1 column (name of group),
// report items have 2 columns (report name and comment)
m_tocTreeWidget->setColumnCount(2);
// headers
QStringList headers;
headers << i18n("Reports") << i18n("Comment");
m_tocTreeWidget->setHeaderLabels(headers);
m_tocTreeWidget->setAlternatingRowColors(true);
m_tocTreeWidget->setSortingEnabled(true);
m_tocTreeWidget->sortByColumn(0, Qt::AscendingOrder);
// for report group items:
// doubleclick toggles the expand-state,
// so avoid any further action in case of doubleclick
// (see slotItemDoubleClicked)
m_tocTreeWidget->setExpandsOnDoubleClick(false);
m_tocTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
m_tocTreeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
m_listTabLayout->addWidget(m_tocTreeWidget);
m_reportTabWidget->addTab(m_listTab, i18n("Reports"));
connect(m_reportTabWidget, SIGNAL(tabCloseRequested(int)),
this, SLOT(slotClose(int)));
connect(m_tocTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
this, SLOT(slotItemDoubleClicked(QTreeWidgetItem*,int)));
connect(m_tocTreeWidget, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(slotListContextMenu(QPoint)));
connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView()));
}
void KReportsView::showEvent(QShowEvent * event)
{
emit aboutToShow();
if (m_needReload) {
loadView();
m_needReload = false;
}
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (tab)
emit reportSelected(tab->report());
else
emit reportSelected(MyMoneyReport());
// don't forget base class implementation
KMyMoneyViewBase::showEvent(event);
}
void KReportsView::slotLoadView()
{
m_needReload = true;
if (isVisible()) {
loadView();
m_needReload = false;
}
}
void KReportsView::loadView()
{
// remember the id of the current selected item
QTreeWidgetItem* item = m_tocTreeWidget->currentItem();
QString selectedItem = (item) ? item->text(0) : QString();
// save expand states of all top-level items
QMap expandStates;
for (int i = 0; i < m_tocTreeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* item = m_tocTreeWidget->topLevelItem(i);
if (item) {
QString itemLabel = item->text(0);
if (item->isExpanded()) {
expandStates.insert(itemLabel, true);
} else {
expandStates.insert(itemLabel, false);
}
}
}
// find the item visible on top
QTreeWidgetItem* visibleTopItem = m_tocTreeWidget->itemAt(0, 0);
// text of column 0 identifies the item visible on top
QString visibleTopItemText;
bool visibleTopItemFound = true;
if (visibleTopItem == NULL) {
visibleTopItemFound = false;
} else {
// this assumes, that all item-texts in column 0 are unique,
// no matter, whether the item is a report- or a group-item
visibleTopItemText = visibleTopItem->text(0);
}
// turn off updates to avoid flickering during reload
//m_reportListView->setUpdatesEnabled(false);
//
// Rebuild the list page
//
m_tocTreeWidget->clear();
// Default Reports
QList defaultreports;
defaultReports(defaultreports);
QList::const_iterator it_group = defaultreports.constBegin();
// the item to be set as current item
QTreeWidgetItem* currentItem = 0L;
// group number, this will be used as sort key for reportgroup items
// we have:
// 1st some default groups
// 2nd a chart group
// 3rd maybe a favorite group
// 4th maybe an orphan group (for old reports)
int defaultGroupNo = 1;
int chartGroupNo = defaultreports.size() + 1;
// group for diagrams
QString groupName = I18N_NOOP("Charts");
TocItemGroup* chartTocItemGroup =
new TocItemGroup(m_tocTreeWidget, chartGroupNo,
i18n(groupName.toLatin1().data()));
m_allTocItemGroups.insert(groupName, chartTocItemGroup);
while (it_group != defaultreports.constEnd()) {
QString groupName = (*it_group).name();
TocItemGroup* defaultTocItemGroup =
new TocItemGroup(m_tocTreeWidget, defaultGroupNo++,
i18n(groupName.toLatin1().data()));
m_allTocItemGroups.insert(groupName, defaultTocItemGroup);
if (groupName == selectedItem) {
currentItem = defaultTocItemGroup;
}
QList::const_iterator it_report = (*it_group).begin();
while (it_report != (*it_group).end()) {
MyMoneyReport report = *it_report;
report.setGroup(groupName);
TocItemReport* reportTocItemReport =
new TocItemReport(defaultTocItemGroup, report);
if (report.name() == selectedItem) {
currentItem = reportTocItemReport;
}
// ALSO place it into the Charts list if it's displayed as a chart by default
if (report.isChartByDefault()) {
new TocItemReport(chartTocItemGroup, report);
}
++it_report;
}
++it_group;
}
// group for custom (favorite) reports
int favoriteGroupNo = chartGroupNo + 1;
groupName = I18N_NOOP("Favorite Reports");
TocItemGroup* favoriteTocItemGroup =
new TocItemGroup(m_tocTreeWidget, favoriteGroupNo,
i18n(groupName.toLatin1().data()));
m_allTocItemGroups.insert(groupName, favoriteTocItemGroup);
TocItemGroup* orphanTocItemGroup = 0;
QList customreports = MyMoneyFile::instance()->reportList();
QList::const_iterator it_report = customreports.constBegin();
while (it_report != customreports.constEnd()) {
MyMoneyReport report = *it_report;
QString groupName = (*it_report).group();
// If this report is in a known group, place it there
// KReportGroupListItem* groupnode = groupitems[(*it_report).group()];
TocItemGroup* groupNode = m_allTocItemGroups[groupName];
if (groupNode) {
new TocItemReport(groupNode, report);
} else {
// otherwise, place it in the orphanage
if (!orphanTocItemGroup) {
// group for orphaned reports
int orphanGroupNo = favoriteGroupNo + 1;
QString groupName = I18N_NOOP("Old Customized Reports");
orphanTocItemGroup =
new TocItemGroup(m_tocTreeWidget, orphanGroupNo,
i18n(groupName.toLatin1().data()));
m_allTocItemGroups.insert(groupName, orphanTocItemGroup);
}
new TocItemReport(orphanTocItemGroup, report);
}
// ALSO place it into the Favorites list if it's a favorite
if ((*it_report).isFavorite()) {
new TocItemReport(favoriteTocItemGroup, report);
}
// ALSO place it into the Charts list if it's displayed as a chart by default
if ((*it_report).isChartByDefault()) {
new TocItemReport(chartTocItemGroup, report);
}
++it_report;
}
//
// Go through the tabs to set their update flag or delete them if needed
//
int index = 1;
while (index < m_reportTabWidget->count()) {
// TODO: Find some way of detecting the file is closed and kill these tabs!!
KReportTab* tab = dynamic_cast(m_reportTabWidget->widget(index));
if (tab->isReadyToDelete() /* || ! reports.count() */) {
delete tab;
--index;
} else {
tab->loadTab();
}
++index;
}
if (visibleTopItemFound) {
// try to find the visibleTopItem that we had at the start of this method
// intentionally not using 'Qt::MatchCaseSensitive' here
// to avoid 'item not found' if someone corrected a typo only
QList visibleTopItemList =
m_tocTreeWidget->findItems(visibleTopItemText,
Qt::MatchFixedString |
Qt::MatchRecursive);
if (visibleTopItemList.isEmpty()) {
// the item could not be found, it was deleted or renamed
visibleTopItemFound = false;
} else {
visibleTopItem = visibleTopItemList.at(0);
if (visibleTopItem == NULL) {
visibleTopItemFound = false;
}
}
}
// adjust column widths,
// but only the first time when the view is loaded,
// maybe the user sets other column widths later,
// so don't disturb him
if (columnsAlreadyAdjusted()) {
// restore expand states of all top-level items
restoreTocExpandState(expandStates);
// restore current item
m_tocTreeWidget->setCurrentItem(currentItem);
// try to scroll to the item visible on top
// when this method started
if (visibleTopItemFound) {
m_tocTreeWidget->scrollToItem(visibleTopItem);
} else {
m_tocTreeWidget->scrollToTop();
}
return;
}
// avoid flickering
m_tocTreeWidget->setUpdatesEnabled(false);
// expand all top-level items
m_tocTreeWidget->expandAll();
// resize columns
m_tocTreeWidget->resizeColumnToContents(0);
m_tocTreeWidget->resizeColumnToContents(1);
// restore expand states of all top-level items
restoreTocExpandState(expandStates);
// restore current item
m_tocTreeWidget->setCurrentItem(currentItem);
// try to scroll to the item visible on top
// when this method started
if (visibleTopItemFound) {
m_tocTreeWidget->scrollToItem(visibleTopItem);
} else {
m_tocTreeWidget->scrollToTop();
}
setColumnsAlreadyAdjusted(true);
m_tocTreeWidget->setUpdatesEnabled(true);
}
void KReportsView::slotOpenUrl(const QUrl &url, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&)
{
QString view = url.fileName();
QString command = QUrlQuery(url).queryItemValue("command");
QString id = QUrlQuery(url).queryItemValue("id");
QString tid = QUrlQuery(url).queryItemValue("tid");
if (view == VIEW_REPORTS) {
if (command.isEmpty()) {
// slotRefreshView();
} else if (command == "print")
slotPrintView();
else if (command == "copy")
slotCopyView();
else if (command == "save")
slotSaveView();
else if (command == "configure")
slotConfigure();
else if (command == "duplicate")
slotDuplicate();
else if (command == "close")
slotCloseCurrent();
else if (command == "delete")
slotDelete();
else
qWarning() << i18n("Unknown command '%1' in KReportsView::slotOpenUrl()", qPrintable(command));
} else if (view == VIEW_LEDGER) {
emit ledgerSelected(id, tid);
} else {
qWarning() << i18n("Unknown view '%1' in KReportsView::slotOpenUrl()", qPrintable(view));
}
}
void KReportsView::slotPrintView()
{
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (tab)
tab->print();
}
void KReportsView::slotCopyView()
{
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (tab)
tab->copyToClipboard();
}
void KReportsView::slotSaveView()
{
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (tab) {
QString filterList = i18nc("CSV (Filefilter)", "CSV files") + " (*.csv)" + ";;" + i18nc("HTML (Filefilter)", "HTML files") + " (*.html)";
QUrl newURL = QFileDialog::getSaveFileUrl(this, i18n("Export as"), QUrl::fromLocalFile(KRecentDirs::dir(":kmymoney-export")), filterList, &m_selectedExportFilter);
if (!newURL.isEmpty()) {
KRecentDirs::add(":kmymoney-export", newURL.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
QString newName = newURL.toDisplayString(QUrl::PreferLocalFile);
if (newName.indexOf('.') == -1)
newName.append(".html");
try {
tab->saveAs(newName, true);
} catch (const MyMoneyException &e) {
KMessageBox::error(this, i18n("Failed to save: %1", e.what()));
}
}
}
}
void KReportsView::slotConfigure()
{
QString cm = "KReportsView::slotConfigure";
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (!tab) {
// nothing to do
return;
}
MyMoneyReport report = tab->report();
if (report.comment() == i18n("Default Report") || report.comment() == i18n("Generated Report")) {
report.setComment(i18n("Custom Report"));
report.setName(i18n("%1 (Customized)", report.name()));
}
QPointer dlg = new KReportConfigurationFilterDlg(report);
if (dlg->exec()) {
MyMoneyReport newreport = dlg->getConfig();
// If this report has an ID, then MODIFY it, otherwise ADD it
MyMoneyFileTransaction ft;
try {
if (! newreport.id().isEmpty()) {
MyMoneyFile::instance()->modifyReport(newreport);
ft.commit();
tab->modifyReport(newreport);
m_reportTabWidget->setTabText(m_reportTabWidget->indexOf(tab), newreport.name());
m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(tab)) ;
} else {
MyMoneyFile::instance()->addReport(newreport);
ft.commit();
QString reportGroupName = newreport.group();
// find report group
TocItemGroup* tocItemGroup = m_allTocItemGroups[reportGroupName];
if (!tocItemGroup) {
QString error = i18n("Could not find reportgroup \"%1\" for report \"%2\".\nPlease report this error to the developer's list: kmymoney-devel@kde.org", reportGroupName, newreport.name());
// write to messagehandler
qWarning() << cm << error;
// also inform user
KMessageBox::error(m_reportTabWidget, error, i18n("Critical Error"));
// cleanup
delete dlg;
return;
}
// do not add TocItemReport to TocItemGroup here,
// this is done in loadView
addReportTab(newreport);
}
} catch (const MyMoneyException &e) {
KMessageBox::error(this, i18n("Failed to configure report: %1", e.what()));
}
}
delete dlg;
}
void KReportsView::slotDuplicate()
{
QString cm = "KReportsView::slotDuplicate";
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (!tab) {
// nothing to do
return;
}
MyMoneyReport dupe = tab->report();
dupe.setName(i18n("Copy of %1", dupe.name()));
if (dupe.comment() == i18n("Default Report"))
dupe.setComment(i18n("Custom Report"));
dupe.clearId();
QPointer dlg = new KReportConfigurationFilterDlg(dupe);
if (dlg->exec()) {
MyMoneyReport newReport = dlg->getConfig();
MyMoneyFileTransaction ft;
try {
MyMoneyFile::instance()->addReport(newReport);
ft.commit();
QString reportGroupName = newReport.group();
// find report group
TocItemGroup* tocItemGroup = m_allTocItemGroups[reportGroupName];
if (!tocItemGroup) {
QString error = i18n("Could not find reportgroup \"%1\" for report \"%2\".\nPlease report this error to the developer's list: kmymoney-devel@kde.org", reportGroupName, newReport.name());
// write to messagehandler
qWarning() << cm << error;
// also inform user
KMessageBox::error(m_reportTabWidget, error, i18n("Critical Error"));
// cleanup
delete dlg;
return;
}
// do not add TocItemReport to TocItemGroup here,
// this is done in loadView
addReportTab(newReport);
} catch (const MyMoneyException &e) {
QString error = i18n("Cannot add report, reason: \"%1\"", e.what());
// write to messagehandler
qWarning() << cm << error;
// also inform user
KMessageBox::error(m_reportTabWidget, error, i18n("Critical Error"));
}
}
delete dlg;
}
void KReportsView::slotDelete()
{
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (!tab) {
// nothing to do
return;
}
MyMoneyReport report = tab->report();
if (! report.id().isEmpty()) {
if (KMessageBox::Continue == deleteReportDialog(report.name())) {
// close the tab and then remove the report so that it is not
// generated again during the following loadView() call
slotClose(m_reportTabWidget->currentIndex());
MyMoneyFileTransaction ft;
MyMoneyFile::instance()->removeReport(report);
ft.commit();
}
} else {
KMessageBox::information(this,
QString("") +
i18n("%1 is a default report, so it cannot be deleted.",
report.name()) + QString(""), i18n("Delete Report?"));
}
}
int KReportsView::deleteReportDialog(const QString &reportName)
{
return KMessageBox::warningContinueCancel(this, QString("") +
i18n("Are you sure you want to delete report %1? There is no way to recover it.",
reportName) + QString(""), i18n("Delete Report?"));
}
void KReportsView::slotOpenReport(const QString& id)
{
if (id.isEmpty()) {
// nothing to do
return;
}
KReportTab* page = 0;
// Find the tab which contains the report
int index = 1;
while (index < m_reportTabWidget->count()) {
KReportTab* current = dynamic_cast(m_reportTabWidget->widget(index));
if (current->report().id() == id) {
page = current;
break;
}
++index;
}
// Show the tab, or create a new one, as needed
if (page)
m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(page));
else
addReportTab(MyMoneyFile::instance()->report(id));
}
void KReportsView::slotOpenReport(const MyMoneyReport& report)
{
qDebug() << Q_FUNC_INFO << " " << report.name();
KReportTab* page = 0;
// Find the tab which contains the report indicated by this list item
int index = 1;
while (index < m_reportTabWidget->count()) {
KReportTab* current = dynamic_cast(m_reportTabWidget->widget(index));
if (current->report().name() == report.name()) {
page = current;
break;
}
++index;
}
// Show the tab, or create a new one, as needed
if (page)
m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(page));
else
addReportTab(report);
}
void KReportsView::slotItemDoubleClicked(QTreeWidgetItem* item, int)
{
TocItem* tocItem = dynamic_cast(item);
if (!tocItem->isReport()) {
// toggle the expanded-state for reportgroup-items
item->setExpanded(item->isExpanded() ? false : true);
// nothing else to do for reportgroup-items
return;
}
TocItemReport* reportTocItem = dynamic_cast(tocItem);
MyMoneyReport& report = reportTocItem->getReport();
KReportTab* page = 0;
// Find the tab which contains the report indicated by this list item
int index = 1;
while (index < m_reportTabWidget->count()) {
KReportTab* current = dynamic_cast(m_reportTabWidget->widget(index));
// If this report has an ID, we'll use the ID to match
if (! report.id().isEmpty()) {
if (current->report().id() == report.id()) {
page = current;
break;
}
}
// Otherwise, use the name to match. THIS ASSUMES that no 2 default reports
// have the same name...but that would be pretty a boneheaded thing to do.
else {
if (current->report().name() == report.name()) {
page = current;
break;
}
}
++index;
}
// Show the tab, or create a new one, as needed
if (page)
m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(page));
else
addReportTab(report);
}
void KReportsView::slotToggleChart()
{
KReportTab* tab = dynamic_cast(m_reportTabWidget->currentWidget());
if (tab)
tab->toggleChart();
}
void KReportsView::slotCloseCurrent()
{
slotClose(m_reportTabWidget->currentIndex());
}
void KReportsView::slotClose(int index)
{
KReportTab* tab = dynamic_cast(m_reportTabWidget->widget(index));
if (tab) {
m_reportTabWidget->removeTab(index);
tab->setReadyToDelete(true);
}
}
void KReportsView::slotCloseAll()
{
KReportTab* tab = dynamic_cast(m_reportTabWidget->widget(1));
while (tab) {
m_reportTabWidget->removeTab(m_reportTabWidget->indexOf(tab));
tab->setReadyToDelete(true);
tab = dynamic_cast(m_reportTabWidget->widget(1));
}
}
void KReportsView::addReportTab(const MyMoneyReport& report)
{
KReportTab* tab = new KReportTab(m_reportTabWidget, report);
connect(tab->control()->buttonChart, SIGNAL(clicked()),
this, SLOT(slotToggleChart()));
connect(tab->control()->buttonConfigure, SIGNAL(clicked()),
this, SLOT(slotConfigure()));
connect(tab->control()->buttonNew, SIGNAL(clicked()),
this, SLOT(slotDuplicate()));
connect(tab->control()->buttonCopy, SIGNAL(clicked()),
this, SLOT(slotCopyView()));
connect(tab->control()->buttonExport, SIGNAL(clicked()),
this, SLOT(slotSaveView()));
connect(tab->control()->buttonDelete, SIGNAL(clicked()),
this, SLOT(slotDelete()));
connect(tab->control()->buttonClose, SIGNAL(clicked()),
this, SLOT(slotCloseCurrent()));
connect(tab->browserExtenstion(), SIGNAL(openUrlRequest(const QUrl &, const KParts::OpenUrlArguments &, const KParts::BrowserArguments &)),
this, SLOT(slotOpenUrl(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)));
// if this is a default report, then you can't delete it!
if (report.id().isEmpty())
tab->control()->buttonDelete->setEnabled(false);
m_reportTabWidget->setCurrentIndex(m_reportTabWidget->indexOf(tab));
}
void KReportsView::slotListContextMenu(const QPoint & p)
{
QTreeWidgetItem *item = m_tocTreeWidget->itemAt(p);
if (!item) {
return;
}
TocItem* tocItem = dynamic_cast(item);
if (!tocItem->isReport()) {
// currently there is no context menu for reportgroup items
return;
}
QMenu* contextmenu = new QMenu(this);
contextmenu->addAction(i18nc("To open a new report", "&Open"),
this, SLOT(slotOpenFromList()));
contextmenu->addAction(i18nc("Configure a report", "&Configure"),
this, SLOT(slotConfigureFromList()));
contextmenu->addAction(i18n("&New report"),
this, SLOT(slotNewFromList()));
// Only add this option if it's a custom report. Default reports cannot be deleted
TocItemReport* reportTocItem = dynamic_cast(tocItem);
MyMoneyReport& report = reportTocItem->getReport();
if (! report.id().isEmpty()) {
contextmenu->addAction(i18n("&Delete"),
this, SLOT(slotDeleteFromList()));
}
contextmenu->popup(m_tocTreeWidget->mapToGlobal(p));
}
void KReportsView::slotOpenFromList()
{
TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem());
if (tocItem)
slotItemDoubleClicked(tocItem, 0);
}
void KReportsView::slotConfigureFromList()
{
TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem());
if (tocItem) {
slotItemDoubleClicked(tocItem, 0);
slotConfigure();
}
}
void KReportsView::slotNewFromList()
{
TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem());
if (tocItem) {
slotItemDoubleClicked(tocItem, 0);
slotDuplicate();
}
}
void KReportsView::slotDeleteFromList()
{
TocItem* tocItem = dynamic_cast(m_tocTreeWidget->currentItem());
if (tocItem) {
TocItemReport* reportTocItem = dynamic_cast(tocItem);
MyMoneyReport& report = reportTocItem->getReport();
// If this report does not have an ID, it's a default report and cannot be deleted
if (! report.id().isEmpty() &&
KMessageBox::Continue == deleteReportDialog(report.name())) {
MyMoneyFileTransaction ft;
MyMoneyFile::instance()->removeReport(report);
ft.commit();
}
}
}
void KReportsView::defaultReports(QList& groups)
{
{
ReportGroup list("Income and Expenses", i18n("Income and Expenses"));
list.push_back(MyMoneyReport(
MyMoneyReport::eExpenseIncome,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentMonth,
MyMoneyReport::eDetailAll,
i18n("Income and Expenses This Month"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eExpenseIncome,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Income and Expenses This Year"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eExpenseIncome,
MyMoneyReport::eYears,
MyMoneyTransactionFilter::allDates,
MyMoneyReport::eDetailAll,
i18n("Income and Expenses By Year"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eExpenseIncome,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last12Months,
MyMoneyReport::eDetailTop,
i18n("Income and Expenses Graph"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartType(MyMoneyReport::eChartLine);
list.back().setChartDataLabels(false);
list.push_back(MyMoneyReport(
MyMoneyReport::eExpenseIncome,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailGroup,
i18n("Income and Expenses Pie Chart"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartType(MyMoneyReport::eChartPie);
list.back().setShowingRowTotals(false);
groups.push_back(list);
}
{
ReportGroup list("Net Worth", i18n("Net Worth"));
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailTop,
i18n("Net Worth By Month"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::today,
MyMoneyReport::eDetailTop,
i18n("Net Worth Today"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eYears,
MyMoneyTransactionFilter::allDates,
MyMoneyReport::eDetailTop,
i18n("Net Worth By Year"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::next7Days,
MyMoneyReport::eDetailTop,
i18n("7-day Cash Flow Forecast"),
i18n("Default Report")
));
list.back().setIncludingSchedules(true);
list.back().setColumnsAreDays(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last12Months,
MyMoneyReport::eDetailTotal,
i18n("Net Worth Graph"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartLine);
list.push_back(MyMoneyReport(
MyMoneyReport::eInstitution,
MyMoneyReport::eQCnone,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailTop,
i18n("Account Balances by Institution"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eAccountType,
MyMoneyReport::eQCnone,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailTop,
i18n("Account Balances by Type"),
i18n("Default Report")
));
groups.push_back(list);
}
{
ReportGroup list("Transactions", i18n("Transactions"));
list.push_back(MyMoneyReport(
MyMoneyReport::eAccount,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag | MyMoneyReport::eQCbalance,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Transactions by Account"),
i18n("Default Report")
));
//list.back().setConvertCurrency(false);
list.push_back(MyMoneyReport(
MyMoneyReport::eCategory,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount | MyMoneyReport::eQCtag,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Transactions by Category"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::ePayee,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Transactions by Payee"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eTag,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Transactions by Tag"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eMonth,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Transactions by Month"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eWeek,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCtag,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Transactions by Week"),
i18n("Default Report")
));
list.push_back(MyMoneyReport(
MyMoneyReport::eAccount,
MyMoneyReport::eQCloan,
MyMoneyTransactionFilter::allDates,
MyMoneyReport::eDetailAll,
i18n("Loan Transactions"),
i18n("Default Report")
));
list.back().setLoansOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAccountReconcile,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance,
MyMoneyTransactionFilter::last3Months,
MyMoneyReport::eDetailAll,
i18n("Transactions by Reconciliation Status"),
i18n("Default Report")
));
groups.push_back(list);
}
{
ReportGroup list("CashFlow", i18n("Cash Flow"));
list.push_back(MyMoneyReport(
MyMoneyReport::eCashFlow,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Cash Flow Transactions This Month"),
i18n("Default Report")
));
groups.push_back(list);
}
{
ReportGroup list("Investments", i18n("Investments"));
list.push_back(MyMoneyReport(
MyMoneyReport::eTopAccount,
MyMoneyReport::eQCaction | MyMoneyReport::eQCshares | MyMoneyReport::eQCprice,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Investment Transactions"),
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAccountByTopAccount,
MyMoneyReport::eQCshares | MyMoneyReport::eQCprice,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Investment Holdings by Account"),
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eEquityType,
MyMoneyReport::eQCshares | MyMoneyReport::eQCprice,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Investment Holdings by Type"),
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAccountByTopAccount,
MyMoneyReport::eQCperformance,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Investment Performance by Account"),
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eEquityType,
MyMoneyReport::eQCperformance,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Investment Performance by Type"),
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
+
+ list.push_back(MyMoneyReport(
+ MyMoneyReport::eAccountByTopAccount,
+ MyMoneyReport::eQCcapitalgain,
+ MyMoneyTransactionFilter::yearToDate,
+ MyMoneyReport::eDetailAll,
+ i18n("Investment Capital Gains by Account"),
+ i18n("Default Report")
+ ));
+ list.back().setInvestmentsOnly(true);
+
+ list.push_back(MyMoneyReport(
+ MyMoneyReport::eEquityType,
+ MyMoneyReport::eQCcapitalgain,
+ MyMoneyTransactionFilter::yearToDate,
+ MyMoneyReport::eDetailAll,
+ i18n("Investment Capital Gains by Type"),
+ i18n("Default Report")
+ ));
+ list.back().setInvestmentsOnly(true);
+
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::today,
MyMoneyReport::eDetailAll,
i18n("Investment Holdings Pie"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartPie);
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last12Months,
MyMoneyReport::eDetailAll,
i18n("Investment Worth Graph"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartLine);
list.back().setColumnsAreDays(true);
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last12Months,
MyMoneyReport::eDetailAll,
i18n("Investment Price Graph"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartLine);
list.back().setColumnsAreDays(true);
list.back().setInvestmentsOnly(true);
list.back().setIncludingBudgetActuals(false);
list.back().setIncludingPrice(true);
list.back().setConvertCurrency(true);
list.back().setChartDataLabels(false);
list.back().setSkipZero(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last12Months,
MyMoneyReport::eDetailAll,
i18n("Investment Moving Average Price Graph"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartLine);
list.back().setColumnsAreDays(true);
list.back().setInvestmentsOnly(true);
list.back().setIncludingBudgetActuals(false);
list.back().setIncludingAveragePrice(true);
list.back().setMovingAverageDays(10);
list.back().setConvertCurrency(true);
list.back().setChartDataLabels(false);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last30Days,
MyMoneyReport::eDetailAll,
i18n("Investment Moving Average"),
i18n("Default Report")
));
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartLine);
list.back().setColumnsAreDays(true);
list.back().setInvestmentsOnly(true);
list.back().setIncludingBudgetActuals(false);
list.back().setIncludingMovingAverage(true);
list.back().setMovingAverageDays(10);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::last30Days,
MyMoneyReport::eDetailAll,
i18n("Investment Moving Average vs Actual"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartLine);
list.back().setColumnsAreDays(true);
list.back().setInvestmentsOnly(true);
list.back().setIncludingBudgetActuals(true);
list.back().setIncludingMovingAverage(true);
list.back().setMovingAverageDays(10);
groups.push_back(list);
}
{
ReportGroup list("Taxes", i18n("Taxes"));
list.push_back(MyMoneyReport(
MyMoneyReport::eCategory,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Tax Transactions by Category"),
i18n("Default Report")
));
list.back().setTax(true);
list.push_back(MyMoneyReport(
MyMoneyReport::ePayee,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory | MyMoneyReport::eQCaccount,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Tax Transactions by Payee"),
i18n("Default Report")
));
list.back().setTax(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eCategory,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount,
MyMoneyTransactionFilter::lastFiscalYear,
MyMoneyReport::eDetailAll,
i18n("Tax Transactions by Category Last Fiscal Year"),
i18n("Default Report")
));
list.back().setTax(true);
list.push_back(MyMoneyReport(
MyMoneyReport::ePayee,
MyMoneyReport::eQCnumber | MyMoneyReport::eQCcategory | MyMoneyReport::eQCaccount,
MyMoneyTransactionFilter::lastFiscalYear,
MyMoneyReport::eDetailAll,
i18n("Tax Transactions by Payee Last Fiscal Year"),
i18n("Default Report")
));
list.back().setTax(true);
groups.push_back(list);
}
{
ReportGroup list("Budgeting", i18n("Budgeting"));
list.push_back(MyMoneyReport(
MyMoneyReport::eBudgetActual,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Budgeted vs. Actual This Year"),
i18n("Default Report")
));
list.back().setShowingRowTotals(true);
list.back().setBudget("Any", true);
list.push_back(MyMoneyReport(
MyMoneyReport::eBudgetActual,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::yearToMonth,
MyMoneyReport::eDetailAll,
i18n("Budgeted vs. Actual This Year (YTM)"),
i18n("Default Report")
));
list.back().setShowingRowTotals(true);
list.back().setBudget("Any", true);
// in case we're in January, we show the last year
if (QDate::currentDate().month() == 1) {
list.back().setDateFilter(MyMoneyTransactionFilter::lastYear);
}
list.push_back(MyMoneyReport(
MyMoneyReport::eBudgetActual,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentMonth,
MyMoneyReport::eDetailAll,
i18n("Monthly Budgeted vs. Actual"),
i18n("Default Report")
));
list.back().setBudget("Any", true);
list.push_back(MyMoneyReport(
MyMoneyReport::eBudgetActual,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentYear,
MyMoneyReport::eDetailAll,
i18n("Yearly Budgeted vs. Actual"),
i18n("Default Report")
));
list.back().setBudget("Any", true);
list.back().setShowingRowTotals(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eBudget,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentMonth,
MyMoneyReport::eDetailAll,
i18n("Monthly Budget"),
i18n("Default Report")
));
list.back().setBudget("Any", false);
list.push_back(MyMoneyReport(
MyMoneyReport::eBudget,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentYear,
MyMoneyReport::eDetailAll,
i18n("Yearly Budget"),
i18n("Default Report")
));
list.back().setBudget("Any", false);
list.back().setShowingRowTotals(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eBudgetActual,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentYear,
MyMoneyReport::eDetailGroup,
i18n("Yearly Budgeted vs Actual Graph"),
i18n("Default Report")
));
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setBudget("Any", true);
list.back().setChartType(MyMoneyReport::eChartLine);
groups.push_back(list);
}
{
ReportGroup list("Forecast", i18n("Forecast"));
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::next12Months,
MyMoneyReport::eDetailTop,
i18n("Forecast By Month"),
i18n("Default Report")
));
list.back().setIncludingForecast(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::nextQuarter,
MyMoneyReport::eDetailTop,
i18n("Forecast Next Quarter"),
i18n("Default Report")
));
list.back().setColumnsAreDays(true);
list.back().setIncludingForecast(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eExpenseIncome,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::currentYear,
MyMoneyReport::eDetailTop,
i18n("Income and Expenses Forecast This Year"),
i18n("Default Report")
));
list.back().setIncludingForecast(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::next3Months,
MyMoneyReport::eDetailTotal,
i18n("Net Worth Forecast Graph"),
i18n("Default Report")
));
list.back().setColumnsAreDays(true);
list.back().setIncludingForecast(true);
list.back().setChartByDefault(true);
list.back().setChartGridLines(false);
list.back().setChartType(MyMoneyReport::eChartLine);
groups.push_back(list);
}
{
ReportGroup list("Information", i18n("General Information"));
list.push_back(MyMoneyReport(
MyMoneyReport::eSchedule,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::next12Months,
MyMoneyReport::eDetailAll,
i18n("Schedule Information"),
i18n("Default Report")
));
list.back().setDetailLevel(MyMoneyReport::eDetailAll);
list.push_back(MyMoneyReport(
MyMoneyReport::eSchedule,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::next12Months,
MyMoneyReport::eDetailAll,
i18n("Schedule Summary Information"),
i18n("Default Report")
));
list.back().setDetailLevel(MyMoneyReport::eDetailTop);
list.push_back(MyMoneyReport(
MyMoneyReport::eAccountInfo,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::today,
MyMoneyReport::eDetailAll,
i18n("Account Information"),
i18n("Default Report")
));
list.back().setConvertCurrency(false);
list.push_back(MyMoneyReport(
MyMoneyReport::eAccountLoanInfo,
MyMoneyReport::eMonths,
MyMoneyTransactionFilter::today,
MyMoneyReport::eDetailAll,
i18n("Loan Information"),
i18n("Default Report")
));
list.back().setConvertCurrency(false);
groups.push_back(list);
}
}
bool KReportsView::columnsAlreadyAdjusted()
{
return m_columnsAlreadyAdjusted;
}
void KReportsView::setColumnsAlreadyAdjusted(bool adjusted)
{
m_columnsAlreadyAdjusted = adjusted;
}
void KReportsView::restoreTocExpandState(QMap& expandStates)
{
for (int i = 0; i < m_tocTreeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* item = m_tocTreeWidget->topLevelItem(i);
if (item) {
QString itemLabel = item->text(0);
if (expandStates.contains(itemLabel)) {
item->setExpanded(expandStates[itemLabel]);
} else {
item->setExpanded(false);
}
}
}
}
// 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