diff --git a/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp b/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp
index 9a46d4add..ce32eed68 100644
--- a/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp
+++ b/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp
@@ -1,717 +1,733 @@
/***************************************************************************
kreportconfigurationdlg.cpp - description
-------------------
begin : Mon Jun 21 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 "kreportconfigurationfilterdlg.h"
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
#include
#include
#include
#include
#include
#include
// ----------------------------------------------------------------------------
// Project Includes
#include
#include
#include
#include
#include
#include
#include
#include "ui_kfindtransactiondlgdecl.h"
#include
#include
#include
#include
#include
#include
+#include
KReportConfigurationFilterDlg::KReportConfigurationFilterDlg(
MyMoneyReport report, QWidget *parent)
: KFindTransactionDlg(parent),
m_tabRowColPivot(0),
m_tabRowColQuery(0),
m_tabChart(0),
m_tabRange(0),
m_initialState(report),
m_currentState(report)
{
//
// Rework labeling
//
setWindowTitle(i18n("Report Configuration"));
delete m_ui->TextLabel1;
//
// Rework the buttons
//
// the Apply button is always enabled
disconnect(SIGNAL(selectionNotEmpty(bool)));
m_ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
KGuiItem::assign(m_ui->buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::ok());
m_ui->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip for report configuration apply button", "Apply the configuration changes to the report"));
//
// Add new tabs
//
m_tabGeneral = new ReportTabGeneral(m_ui->m_criteriaTab);
m_ui->m_criteriaTab->insertTab(0, m_tabGeneral, i18nc("General tab", "General"));
if (m_initialState.reportType() == MyMoneyReport::ePivotTable) {
m_tabRowColPivot = new ReportTabRowColPivot(m_ui->m_criteriaTab);
m_ui->m_criteriaTab->insertTab(1, m_tabRowColPivot, i18n("Rows/Columns"));
m_tabChart = new ReportTabChart(m_ui->m_criteriaTab);
m_ui->m_criteriaTab->insertTab(2, m_tabChart, i18n("Chart"));
m_tabRange = new ReportTabRange(m_ui->m_criteriaTab);
m_ui->m_criteriaTab->insertTab(3, m_tabRange, i18n("Range"));
// date tab is going to be replaced by range tab, so delete it
m_ui->dateRangeLayout->removeWidget(m_dateRange);
m_dateRange->deleteLater();
m_ui->m_criteriaTab->removeTab(m_ui->m_criteriaTab->indexOf(m_ui->m_dateTab));
m_ui->m_dateTab->deleteLater();
m_dateRange = m_tabRange->m_dateRange;
connect(m_tabRange->ui->m_comboColumns, SIGNAL(activated(int)), this, SLOT(slotColumnTypeChanged(int)));
connect(m_tabRange->ui->m_comboColumns, SIGNAL(activated(int)), this, SLOT(slotUpdateColumnsCombo()));
connect(m_tabChart->ui->m_logYaxis, SIGNAL(stateChanged(int)), this, SLOT(slotLogAxisChanged(int)));
connect(m_tabRowColPivot->ui->m_comboRows, SIGNAL(activated(int)), this, SLOT(slotRowTypeChanged(int)));
connect(m_tabRowColPivot->ui->m_comboRows, SIGNAL(activated(int)), this, SLOT(slotUpdateColumnsCombo()));
//control the state of the includeTransfer check
connect(m_ui->m_categoriesView, SIGNAL(stateChanged()), this, SLOT(slotUpdateCheckTransfers()));
} else if (m_initialState.reportType() == MyMoneyReport::eQueryTable) {
// eInvestmentHoldings is a special-case report, and you cannot configure the
// rows & columns of that report.
if (m_initialState.rowType() < MyMoneyReport::eAccountByTopAccount) {
m_tabRowColQuery = new ReportTabRowColQuery(m_ui->m_criteriaTab);
m_ui->m_criteriaTab->insertTab(1, m_tabRowColQuery, i18n("Rows/Columns"));
}
+ if (m_initialState.queryColumns() & MyMoneyReport::eQCcapitalgain) {
+ m_tabCapitalGain = new ReportTabCapitalGain(m_ui->m_criteriaTab);
+ m_ui->m_criteriaTab->insertTab(1, m_tabCapitalGain, i18n("Report"));
+ }
}
m_ui->m_criteriaTab->setCurrentIndex(m_ui->m_criteriaTab->indexOf(m_tabGeneral));
m_ui->m_criteriaTab->setMinimumSize(500, 200);
QList list = MyMoneyFile::instance()->budgetList();
QList::const_iterator it_b;
for (it_b = list.constBegin(); it_b != list.constEnd(); ++it_b) {
m_budgets.push_back(*it_b);
}
//
// Now set up the widgets with proper values
//
slotReset();
}
KReportConfigurationFilterDlg::~KReportConfigurationFilterDlg()
{
}
void KReportConfigurationFilterDlg::slotSearch()
{
// setup the filter from the dialog widgets
setupFilter();
// Copy the m_filter over to the filter part of m_currentConfig.
m_currentState.assignFilter(m_filter);
// Then extract the report properties
m_currentState.setName(m_tabGeneral->ui->m_editName->text());
m_currentState.setComment(m_tabGeneral->ui->m_editComment->text());
m_currentState.setConvertCurrency(m_tabGeneral->ui->m_checkCurrency->isChecked());
m_currentState.setFavorite(m_tabGeneral->ui->m_checkFavorite->isChecked());
m_currentState.setSkipZero(m_tabGeneral->ui->m_skipZero->isChecked());
if (m_tabRowColPivot) {
MyMoneyReport::EDetailLevel dl[4] = { MyMoneyReport::eDetailAll, MyMoneyReport::eDetailTop, MyMoneyReport::eDetailGroup, MyMoneyReport::eDetailTotal };
m_currentState.setDetailLevel(dl[m_tabRowColPivot->ui->m_comboDetail->currentIndex()]);
// modify the rowtype only if the widget is enabled
if (m_tabRowColPivot->ui->m_comboRows->isEnabled()) {
MyMoneyReport::ERowType rt[2] = { MyMoneyReport::eExpenseIncome, MyMoneyReport::eAssetLiability };
m_currentState.setRowType(rt[m_tabRowColPivot->ui->m_comboRows->currentIndex()]);
}
m_currentState.setShowingRowTotals(false);
if (m_tabRowColPivot->ui->m_comboRows->currentIndex() == 0)
m_currentState.setShowingRowTotals(m_tabRowColPivot->ui->m_checkTotalColumn->isChecked());
m_currentState.setIncludingSchedules(m_tabRowColPivot->ui->m_checkScheduled->isChecked());
m_currentState.setIncludingTransfers(m_tabRowColPivot->ui->m_checkTransfers->isChecked());
m_currentState.setIncludingUnusedAccounts(m_tabRowColPivot->ui->m_checkUnused->isChecked());
if (m_tabRowColPivot->ui->m_comboBudget->isEnabled()) {
m_currentState.setBudget(m_budgets[m_tabRowColPivot->ui->m_comboBudget->currentItem()].id(), m_initialState.rowType() == MyMoneyReport::eBudgetActual);
} else {
m_currentState.setBudget(QString(), false);
}
//set moving average days
if (m_tabRowColPivot->ui->m_movingAverageDays->isEnabled()) {
m_currentState.setMovingAverageDays(m_tabRowColPivot->ui->m_movingAverageDays->value());
}
} else if (m_tabRowColQuery) {
MyMoneyReport::ERowType rtq[8] = { MyMoneyReport::eCategory, MyMoneyReport::eTopCategory, MyMoneyReport::eTag, MyMoneyReport::ePayee, MyMoneyReport::eAccount, MyMoneyReport::eTopAccount, MyMoneyReport::eMonth, MyMoneyReport::eWeek };
m_currentState.setRowType(rtq[m_tabRowColQuery->ui->m_comboOrganizeBy->currentIndex()]);
unsigned qc = MyMoneyReport::eQCnone;
if (m_currentState.queryColumns() & MyMoneyReport::eQCloan)
// once a loan report, always a loan report
qc = MyMoneyReport::eQCloan;
if (m_tabRowColQuery->ui->m_checkNumber->isChecked())
qc |= MyMoneyReport::eQCnumber;
if (m_tabRowColQuery->ui->m_checkPayee->isChecked())
qc |= MyMoneyReport::eQCpayee;
if (m_tabRowColQuery->ui->m_checkTag->isChecked())
qc |= MyMoneyReport::eQCtag;
if (m_tabRowColQuery->ui->m_checkCategory->isChecked())
qc |= MyMoneyReport::eQCcategory;
if (m_tabRowColQuery->ui->m_checkMemo->isChecked())
qc |= MyMoneyReport::eQCmemo;
if (m_tabRowColQuery->ui->m_checkAccount->isChecked())
qc |= MyMoneyReport::eQCaccount;
if (m_tabRowColQuery->ui->m_checkReconciled->isChecked())
qc |= MyMoneyReport::eQCreconciled;
if (m_tabRowColQuery->ui->m_checkAction->isChecked())
qc |= MyMoneyReport::eQCaction;
if (m_tabRowColQuery->ui->m_checkShares->isChecked())
qc |= MyMoneyReport::eQCshares;
if (m_tabRowColQuery->ui->m_checkPrice->isChecked())
qc |= MyMoneyReport::eQCprice;
if (m_tabRowColQuery->ui->m_checkBalance->isChecked())
qc |= MyMoneyReport::eQCbalance;
m_currentState.setQueryColumns(static_cast(qc));
m_currentState.setTax(m_tabRowColQuery->ui->m_checkTax->isChecked());
m_currentState.setInvestmentsOnly(m_tabRowColQuery->ui->m_checkInvestments->isChecked());
m_currentState.setLoansOnly(m_tabRowColQuery->ui->m_checkLoans->isChecked());
m_currentState.setDetailLevel(m_tabRowColQuery->ui->m_checkHideSplitDetails->isChecked() ?
MyMoneyReport::eDetailNone : MyMoneyReport::eDetailAll);
m_currentState.setHideTransactions(m_tabRowColQuery->ui->m_checkHideTransactions->isChecked());
}
if (m_tabChart) {
MyMoneyReport::EChartType ct[5] = { MyMoneyReport::eChartLine, MyMoneyReport::eChartBar, MyMoneyReport::eChartStackedBar, MyMoneyReport::eChartPie, MyMoneyReport::eChartRing };
m_currentState.setChartType(ct[m_tabChart->ui->m_comboType->currentIndex()]);
m_currentState.setChartCHGridLines(m_tabChart->ui->m_checkCHGridLines->isChecked());
m_currentState.setChartSVGridLines(m_tabChart->ui->m_checkSVGridLines->isChecked());
m_currentState.setChartDataLabels(m_tabChart->ui->m_checkValues->isChecked());
m_currentState.setChartByDefault(m_tabChart->ui->m_checkShowChart->isChecked());
m_currentState.setChartLineWidth(m_tabChart->ui->m_lineWidth->value());
m_currentState.setLogYAxis(m_tabChart->ui->m_logYaxis->isChecked());
}
if (m_tabRange) {
m_currentState.setDataRangeStart(m_tabRange->ui->m_dataRangeStart->text());
m_currentState.setDataRangeEnd(m_tabRange->ui->m_dataRangeEnd->text());
m_currentState.setDataMajorTick(m_tabRange->ui->m_dataMajorTick->text());
m_currentState.setDataMinorTick(m_tabRange->ui->m_dataMinorTick->text());
m_currentState.setYLabelsPrecision(m_tabRange->ui->m_yLabelsPrecision->value());
m_currentState.setDataFilter((MyMoneyReport::dataOptionE)m_tabRange->ui->m_dataLock->currentIndex());
MyMoneyReport::EColumnType ct[6] = { MyMoneyReport::eDays, MyMoneyReport::eWeeks, MyMoneyReport::eMonths, MyMoneyReport::eBiMonths, MyMoneyReport::eQuarters, MyMoneyReport::eYears };
bool dy[6] = { true, true, false, false, false, false };
m_currentState.setColumnType(ct[m_tabRange->ui->m_comboColumns->currentIndex()]);
//TODO (Ace) This should be implicit in the call above. MMReport needs fixin'
m_currentState.setColumnsAreDays(dy[m_tabRange->ui->m_comboColumns->currentIndex()]);
}
// setup the date lock
MyMoneyTransactionFilter::dateOptionE range = m_dateRange->m_ui->m_dateRange->currentItem();
m_currentState.setDateFilter(range);
+ if (m_tabCapitalGain) {
+ m_currentState.setTermSeparator(m_tabCapitalGain->ui->m_termSeparator->date());
+ m_currentState.setShowSTLTCapitalGains(m_tabCapitalGain->ui->m_showSTLTCapitalGains->isChecked());
+ m_currentState.setSettlementPeriod(m_tabCapitalGain->ui->m_settlementPeriod->value());
+ }
done(true);
}
void KReportConfigurationFilterDlg::slotRowTypeChanged(int row)
{
m_tabRowColPivot->ui->m_checkTotalColumn->setEnabled(row == 0);
}
void KReportConfigurationFilterDlg::slotColumnTypeChanged(int row)
{
if ((m_tabRowColPivot->ui->m_comboBudget->isEnabled() && row < 2)) {
m_tabRange->ui->m_comboColumns->setCurrentItem(i18nc("@item the columns will display monthly data", "Monthly"), false);
}
}
void KReportConfigurationFilterDlg::slotUpdateColumnsCombo()
{
const int monthlyIndex = 2;
const int incomeExpenseIndex = 0;
const bool isIncomeExpenseForecast = m_currentState.isIncludingForecast() && m_tabRowColPivot->ui->m_comboRows->currentIndex() == incomeExpenseIndex;
if (isIncomeExpenseForecast && m_tabRange->ui->m_comboColumns->currentIndex() != monthlyIndex) {
m_tabRange->ui->m_comboColumns->setCurrentItem(i18nc("@item the columns will display monthly data", "Monthly"), false);
}
}
void KReportConfigurationFilterDlg::slotLogAxisChanged(int state)
{
if (state == Qt::Checked)
m_tabRange->setRangeLogarythmic(true);
else
m_tabRange->setRangeLogarythmic(false);
}
void KReportConfigurationFilterDlg::slotReset()
{
//
// Set up the widget from the initial filter
//
m_currentState = m_initialState;
//
// Report Properties
//
m_tabGeneral->ui->m_editName->setText(m_initialState.name());
m_tabGeneral->ui->m_editComment->setText(m_initialState.comment());
m_tabGeneral->ui->m_checkCurrency->setChecked(m_initialState.isConvertCurrency());
m_tabGeneral->ui->m_checkFavorite->setChecked(m_initialState.isFavorite());
if (m_initialState.isIncludingPrice() || m_initialState.isSkippingZero()) {
m_tabGeneral->ui->m_skipZero->setChecked(m_initialState.isSkippingZero());
} else {
m_tabGeneral->ui->m_skipZero->setEnabled(false);
}
if (m_tabRowColPivot) {
KComboBox *combo = m_tabRowColPivot->ui->m_comboDetail;
switch (m_initialState.detailLevel()) {
case MyMoneyReport::eDetailNone:
case MyMoneyReport::eDetailEnd:
case MyMoneyReport::eDetailAll:
combo->setCurrentItem(i18nc("All accounts", "All"), false);
break;
case MyMoneyReport::eDetailTop:
combo->setCurrentItem(i18n("Top-Level"), false);
break;
case MyMoneyReport::eDetailGroup:
combo->setCurrentItem(i18n("Groups"), false);
break;
case MyMoneyReport::eDetailTotal:
combo->setCurrentItem(i18n("Totals"), false);
break;
}
combo = m_tabRowColPivot->ui->m_comboRows;
switch (m_initialState.rowType()) {
case MyMoneyReport::eExpenseIncome:
case MyMoneyReport::eBudget:
case MyMoneyReport::eBudgetActual:
combo->setCurrentItem(i18n("Income & Expenses"), false); // income / expense
break;
default:
combo->setCurrentItem(i18n("Assets & Liabilities"), false); // asset / liability
break;
}
m_tabRowColPivot->ui->m_checkTotalColumn->setChecked(m_initialState.isShowingRowTotals());
slotRowTypeChanged(combo->currentIndex());
//load budgets combo
if (m_initialState.rowType() == MyMoneyReport::eBudget
|| m_initialState.rowType() == MyMoneyReport::eBudgetActual) {
m_tabRowColPivot->ui->m_comboRows->setEnabled(false);
m_tabRowColPivot->ui->m_budgetFrame->setEnabled(!m_budgets.empty());
int i = 0;
for (QVector::const_iterator it_b = m_budgets.constBegin(); it_b != m_budgets.constEnd(); ++it_b) {
m_tabRowColPivot->ui->m_comboBudget->insertItem((*it_b).name(), i);
//set the current selected item
if ((m_initialState.budget() == "Any" && (*it_b).budgetStart().year() == QDate::currentDate().year())
|| m_initialState.budget() == (*it_b).id())
m_tabRowColPivot->ui->m_comboBudget->setCurrentItem(i);
i++;
}
}
//set moving average days spinbox
QSpinBox *spinbox = m_tabRowColPivot->ui->m_movingAverageDays;
spinbox->setEnabled(m_initialState.isIncludingMovingAverage());
if (m_initialState.isIncludingMovingAverage()) {
spinbox->setValue(m_initialState.movingAverageDays());
}
m_tabRowColPivot->ui->m_checkScheduled->setChecked(m_initialState.isIncludingSchedules());
m_tabRowColPivot->ui->m_checkTransfers->setChecked(m_initialState.isIncludingTransfers());
m_tabRowColPivot->ui->m_checkUnused->setChecked(m_initialState.isIncludingUnusedAccounts());
} else if (m_tabRowColQuery) {
KComboBox *combo = m_tabRowColQuery->ui->m_comboOrganizeBy;
switch (m_initialState.rowType()) {
case MyMoneyReport::eNoColumns:
case MyMoneyReport::eCategory:
combo->setCurrentItem(i18n("Categories"), false);
break;
case MyMoneyReport::eTopCategory:
combo->setCurrentItem(i18n("Top Categories"), false);
break;
case MyMoneyReport::eTag:
combo->setCurrentItem(i18n("Tags"), false);
break;
case MyMoneyReport::ePayee:
combo->setCurrentItem(i18n("Payees"), false);
break;
case MyMoneyReport::eAccount:
combo->setCurrentItem(i18n("Accounts"), false);
break;
case MyMoneyReport::eTopAccount:
combo->setCurrentItem(i18n("Top Accounts"), false);
break;
case MyMoneyReport::eMonth:
combo->setCurrentItem(i18n("Month"), false);
break;
case MyMoneyReport::eWeek:
combo->setCurrentItem(i18n("Week"), false);
break;
default:
throw MYMONEYEXCEPTION("KReportConfigurationFilterDlg::slotReset(): QueryTable report has invalid rowtype");
}
unsigned qc = m_initialState.queryColumns();
m_tabRowColQuery->ui->m_checkNumber->setChecked(qc & MyMoneyReport::eQCnumber);
m_tabRowColQuery->ui->m_checkPayee->setChecked(qc & MyMoneyReport::eQCpayee);
m_tabRowColQuery->ui->m_checkTag->setChecked(qc & MyMoneyReport::eQCtag);
m_tabRowColQuery->ui->m_checkCategory->setChecked(qc & MyMoneyReport::eQCcategory);
m_tabRowColQuery->ui->m_checkMemo->setChecked(qc & MyMoneyReport::eQCmemo);
m_tabRowColQuery->ui->m_checkAccount->setChecked(qc & MyMoneyReport::eQCaccount);
m_tabRowColQuery->ui->m_checkReconciled->setChecked(qc & MyMoneyReport::eQCreconciled);
m_tabRowColQuery->ui->m_checkAction->setChecked(qc & MyMoneyReport::eQCaction);
m_tabRowColQuery->ui->m_checkShares->setChecked(qc & MyMoneyReport::eQCshares);
m_tabRowColQuery->ui->m_checkPrice->setChecked(qc & MyMoneyReport::eQCprice);
m_tabRowColQuery->ui->m_checkBalance->setChecked(qc & MyMoneyReport::eQCbalance);
m_tabRowColQuery->ui->m_checkTax->setChecked(m_initialState.isTax());
m_tabRowColQuery->ui->m_checkInvestments->setChecked(m_initialState.isInvestmentsOnly());
m_tabRowColQuery->ui->m_checkLoans->setChecked(m_initialState.isLoansOnly());
m_tabRowColQuery->ui->m_checkHideTransactions->setChecked(m_initialState.isHideTransactions());
m_tabRowColQuery->ui->m_checkHideSplitDetails->setEnabled(!m_initialState.isHideTransactions());
m_tabRowColQuery->ui->m_checkHideSplitDetails->setChecked
(m_initialState.detailLevel() == MyMoneyReport::eDetailNone || m_initialState.isHideTransactions());
}
if (m_tabChart) {
KMyMoneyGeneralCombo* combo = m_tabChart->ui->m_comboType;
switch (m_initialState.chartType()) {
case MyMoneyReport::eChartNone:
combo->setCurrentItem(MyMoneyReport::eChartLine);
break;
case MyMoneyReport::eChartLine:
case MyMoneyReport::eChartBar:
case MyMoneyReport::eChartStackedBar:
case MyMoneyReport::eChartPie:
case MyMoneyReport::eChartRing:
combo->setCurrentItem(m_initialState.chartType());
break;
case MyMoneyReport::eChartEnd:
throw MYMONEYEXCEPTION("KReportConfigurationFilterDlg::slotReset(): Report has invalid charttype");
}
m_tabChart->ui->m_checkCHGridLines->setChecked(m_initialState.isChartCHGridLines());
m_tabChart->ui->m_checkSVGridLines->setChecked(m_initialState.isChartSVGridLines());
m_tabChart->ui->m_checkValues->setChecked(m_initialState.isChartDataLabels());
m_tabChart->ui->m_checkShowChart->setChecked(m_initialState.isChartByDefault());
m_tabChart->ui->m_lineWidth->setValue(m_initialState.chartLineWidth());
m_tabChart->ui->m_logYaxis->setChecked(m_initialState.isLogYAxis());
}
if (m_tabRange) {
m_tabRange->ui->m_dataRangeStart->setText(m_initialState.dataRangeStart());
m_tabRange->ui->m_dataRangeEnd->setText(m_initialState.dataRangeEnd());
m_tabRange->ui->m_dataMajorTick->setText(m_initialState.dataMajorTick());
m_tabRange->ui->m_dataMinorTick->setText(m_initialState.dataMinorTick());
m_tabRange->ui->m_yLabelsPrecision->setValue(m_initialState.yLabelsPrecision());
m_tabRange->ui->m_dataLock->setCurrentIndex((int)m_initialState.dataFilter());
KComboBox *combo = m_tabRange->ui->m_comboColumns;
if (m_initialState.isColumnsAreDays()) {
switch (m_initialState.columnType()) {
case MyMoneyReport::eNoColumns:
case MyMoneyReport::eDays:
combo->setCurrentItem(i18nc("@item the columns will display daily data", "Daily"), false);
break;
case MyMoneyReport::eWeeks:
combo->setCurrentItem(i18nc("@item the columns will display weekly data", "Weekly"), false);
break;
default:
break;
}
} else {
switch (m_initialState.columnType()) {
case MyMoneyReport::eNoColumns:
case MyMoneyReport::eMonths:
combo->setCurrentItem(i18nc("@item the columns will display monthly data", "Monthly"), false);
break;
case MyMoneyReport::eBiMonths:
combo->setCurrentItem(i18nc("@item the columns will display bi-monthly data", "Bi-Monthly"), false);
break;
case MyMoneyReport::eQuarters:
combo->setCurrentItem(i18nc("@item the columns will display quarterly data", "Quarterly"), false);
break;
case MyMoneyReport::eYears:
combo->setCurrentItem(i18nc("@item the columns will display yearly data", "Yearly"), false);
break;
default:
break;
}
}
}
+ if (m_tabCapitalGain) {
+ m_tabCapitalGain->ui->m_termSeparator->setDate(m_initialState.termSeparator());
+ m_tabCapitalGain->ui->m_showSTLTCapitalGains->setChecked(m_initialState.isShowingSTLTCapitalGains());
+ m_tabCapitalGain->ui->m_settlementPeriod->setValue(m_initialState.settlementPeriod());
+ }
+
//
// Text Filter
//
QRegExp textfilter;
if (m_initialState.textFilter(textfilter)) {
m_ui->m_textEdit->setText(textfilter.pattern());
m_ui->m_caseSensitive->setChecked(Qt::CaseSensitive == textfilter.caseSensitivity());
m_ui->m_regExp->setChecked(QRegExp::RegExp == textfilter.patternSyntax());
m_ui->m_textNegate->setCurrentIndex(m_initialState.isInvertingText());
}
//
// Type & State Filters
//
int type;
if (m_initialState.firstType(type))
m_ui->m_typeBox->setCurrentIndex(type);
int state;
if (m_initialState.firstState(state))
m_ui->m_stateBox->setCurrentIndex(state);
//
// Number Filter
//
QString nrFrom, nrTo;
if (m_initialState.numberFilter(nrFrom, nrTo)) {
if (nrFrom == nrTo) {
m_ui->m_nrEdit->setEnabled(true);
m_ui->m_nrFromEdit->setEnabled(false);
m_ui->m_nrToEdit->setEnabled(false);
m_ui->m_nrEdit->setText(nrFrom);
m_ui->m_nrFromEdit->setText(QString());
m_ui->m_nrToEdit->setText(QString());
m_ui->m_nrButton->setChecked(true);
m_ui->m_nrRangeButton->setChecked(false);
} else {
m_ui->m_nrEdit->setEnabled(false);
m_ui->m_nrFromEdit->setEnabled(true);
m_ui->m_nrToEdit->setEnabled(false);
m_ui->m_nrEdit->setText(QString());
m_ui->m_nrFromEdit->setText(nrFrom);
m_ui->m_nrToEdit->setText(nrTo);
m_ui->m_nrButton->setChecked(false);
m_ui->m_nrRangeButton->setChecked(true);
}
} else {
m_ui->m_nrEdit->setEnabled(true);
m_ui->m_nrFromEdit->setEnabled(false);
m_ui->m_nrToEdit->setEnabled(false);
m_ui->m_nrEdit->setText(QString());
m_ui->m_nrFromEdit->setText(QString());
m_ui->m_nrToEdit->setText(QString());
m_ui->m_nrButton->setChecked(true);
m_ui->m_nrRangeButton->setChecked(false);
}
//
// Amount Filter
//
MyMoneyMoney from, to;
if (m_initialState.amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&);
if (from == to) {
m_ui->m_amountEdit->setEnabled(true);
m_ui->m_amountFromEdit->setEnabled(false);
m_ui->m_amountToEdit->setEnabled(false);
m_ui->m_amountEdit->loadText(QString::number(from.toDouble()));
m_ui->m_amountFromEdit->loadText(QString());
m_ui->m_amountToEdit->loadText(QString());
m_ui->m_amountButton->setChecked(true);
m_ui->m_amountRangeButton->setChecked(false);
} else {
m_ui->m_amountEdit->setEnabled(false);
m_ui->m_amountFromEdit->setEnabled(true);
m_ui->m_amountToEdit->setEnabled(true);
m_ui->m_amountEdit->loadText(QString());
m_ui->m_amountFromEdit->loadText(QString::number(from.toDouble()));
m_ui->m_amountToEdit->loadText(QString::number(to.toDouble()));
m_ui->m_amountButton->setChecked(false);
m_ui->m_amountRangeButton->setChecked(true);
}
} else {
m_ui->m_amountEdit->setEnabled(true);
m_ui->m_amountFromEdit->setEnabled(false);
m_ui->m_amountToEdit->setEnabled(false);
m_ui->m_amountEdit->loadText(QString());
m_ui->m_amountFromEdit->loadText(QString());
m_ui->m_amountToEdit->loadText(QString());
m_ui->m_amountButton->setChecked(true);
m_ui->m_amountRangeButton->setChecked(false);
}
//
// Payees Filter
//
QStringList payees;
if (m_initialState.payees(payees)) {
if (payees.empty()) {
m_ui->m_emptyPayeesButton->setChecked(true);
} else {
selectAllItems(m_ui->m_payeesView, false);
selectItems(m_ui->m_payeesView, payees, true);
}
} else {
selectAllItems(m_ui->m_payeesView, true);
}
//
// Tags Filter
//
QStringList tags;
if (m_initialState.tags(tags)) {
if (tags.empty()) {
m_ui->m_emptyTagsButton->setChecked(true);
} else {
selectAllItems(m_ui->m_tagsView, false);
selectItems(m_ui->m_tagsView, tags, true);
}
} else {
selectAllItems(m_ui->m_tagsView, true);
}
//
// Accounts Filter
//
QStringList accounts;
if (m_initialState.accounts(accounts)) {
m_ui->m_accountsView->selectAllItems(false);
m_ui->m_accountsView->selectItems(accounts, true);
} else
m_ui->m_accountsView->selectAllItems(true);
//
// Categories Filter
//
if (m_initialState.categories(accounts)) {
m_ui->m_categoriesView->selectAllItems(false);
m_ui->m_categoriesView->selectItems(accounts, true);
} else
m_ui->m_categoriesView->selectAllItems(true);
//
// Date Filter
//
// the following call implies a call to slotUpdateSelections,
// that's why we call it last
m_initialState.updateDateFilter();
QDate dateFrom, dateTo;
if (m_initialState.dateFilter(dateFrom, dateTo)) {
if (m_initialState.isDateUserDefined()) {
m_dateRange->m_ui->m_dateRange->setCurrentItem(MyMoneyTransactionFilter::userDefined);
m_dateRange->m_ui->m_fromDate->setDate(dateFrom);
m_dateRange->m_ui->m_toDate->setDate(dateTo);
} else {
m_dateRange->m_ui->m_fromDate->setDate(dateFrom);
m_dateRange->m_ui->m_toDate->setDate(dateTo);
m_dateRange->slotDateChanged();
}
} else {
m_dateRange->m_ui->m_dateRange->setCurrentItem(MyMoneyTransactionFilter::allDates);
m_dateRange->slotDateRangeChanged(MyMoneyTransactionFilter::allDates);
}
slotRightSize();
}
void KReportConfigurationFilterDlg::slotDateChanged()
{
if (m_dateRange->m_ui->m_dateRange->currentItem() != MyMoneyTransactionFilter::userDefined) {
m_dateRange->slotDateChanged();
}
slotUpdateSelections();
}
void KReportConfigurationFilterDlg::slotShowHelp()
{
KHelpClient::invokeHelp("details.reports.config");
}
//TODO Fix the reports and engine to include transfers even if categories are filtered - bug #1523508
void KReportConfigurationFilterDlg::slotUpdateCheckTransfers()
{
QCheckBox* cb = m_tabRowColPivot->ui->m_checkTransfers;
if (!m_ui->m_categoriesView->allItemsSelected()) {
cb->setChecked(false);
cb->setDisabled(true);
} else {
cb->setEnabled(true);
}
}
diff --git a/kmymoney/dialogs/kreportconfigurationfilterdlg.h b/kmymoney/dialogs/kreportconfigurationfilterdlg.h
index e30a13752..56f512e67 100644
--- a/kmymoney/dialogs/kreportconfigurationfilterdlg.h
+++ b/kmymoney/dialogs/kreportconfigurationfilterdlg.h
@@ -1,94 +1,95 @@
/***************************************************************************
kreportconfigurationdlg.h - description
-------------------
begin : Mon Jun 21 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. *
* *
***************************************************************************/
#ifndef KREPORTCONFIGURATIONFILTERDLG_H
#define KREPORTCONFIGURATIONFILTERDLG_H
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
// ----------------------------------------------------------------------------
// Project Includes
#include "kfindtransactiondlg.h"
#include "mymoneyreport.h"
#include "mymoneybudget.h"
#include "reporttabimpl.h"
class ReportTabGeneral;
class ReportTabRowColPivot;
class ReportTabRowColQuery;
class ReportTabChart;
class ReportTabRange;
class MyMoneyBudget;
/**
* @author Ace Jones
*/
class KReportConfigurationFilterDlg : public KFindTransactionDlg
{
Q_OBJECT
public:
explicit KReportConfigurationFilterDlg(MyMoneyReport report, QWidget *parent = 0);
~KReportConfigurationFilterDlg();
const MyMoneyReport& getConfig() const {
return m_currentState;
}
protected:
QPointer m_tabGeneral;
QPointer m_tabRowColPivot;
QPointer m_tabRowColQuery;
QPointer m_tabChart;
QPointer m_tabRange;
+ QPointer m_tabCapitalGain;
MyMoneyReport m_initialState;
MyMoneyReport m_currentState;
protected slots:
void slotRowTypeChanged(int);
void slotColumnTypeChanged(int);
void slotReset();
void slotSearch();
void slotShowHelp();
/**
* This is to enable/disable the check to Include Transfers based on whether Categories are filtered or not.
* This is because if Categories are filtered, transfers will not be included anyway
*/
virtual void slotDateChanged();
void slotUpdateCheckTransfers();
void slotUpdateColumnsCombo();
void slotLogAxisChanged(int state);
private:
QVector m_budgets;
};
#endif
diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp
index b45f23cd7..29c6bfcf1 100644
--- a/kmymoney/mymoney/mymoneyreport.cpp
+++ b/kmymoney/mymoney/mymoneyreport.cpp
@@ -1,838 +1,856 @@
/***************************************************************************
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,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 kDataLockText = QString("automatic,userdefined").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_hideTransactions(false),
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(MyMoneyTransactionFilter::userDefined),
m_accountGroupFilter(false),
m_chartType(eChartLine),
m_chartDataLabels(true),
m_chartCHGridLines(true),
m_chartSVGridLines(true),
m_chartByDefault(false),
m_logYaxis(false),
m_dataLock(MyMoneyReport::automatic),
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_settlementPeriod(3),
+ m_showSTLTCapitalGains(false),
+ m_tseparator(QDate::currentDate().addYears(-1)),
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_hideTransactions(false),
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_chartCHGridLines(true),
m_chartSVGridLines(true),
m_chartByDefault(false),
m_logYaxis(false),
m_dataLock(MyMoneyReport::automatic),
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_settlementPeriod(3),
+ m_showSTLTCapitalGains(false),
+ m_tseparator(QDate::currentDate().addYears(-1)),
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("hidetransactions", m_hideTransactions);
e.setAttribute("rowtype", kRowTypeText[m_rowType]);
e.setAttribute("datelock", kDateLockText[m_dateLock]);
e.setAttribute("datalock", kDataLockText[m_dataLock]);
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("chartchgridlines", m_chartCHGridLines);
e.setAttribute("chartsvgridlines", m_chartSVGridLines);
e.setAttribute("chartbydefault", m_chartByDefault);
e.setAttribute("chartlinewidth", m_chartLineWidth);
e.setAttribute("logYaxis", m_logYaxis);
e.setAttribute("dataRangeStart", m_dataRangeStart);
e.setAttribute("dataRangeEnd", m_dataRangeEnd);
e.setAttribute("dataMajorTick", m_dataMajorTick);
e.setAttribute("dataMinorTick", m_dataMinorTick);
e.setAttribute("yLabelsPrecision", m_yLabelsPrecision);
e.setAttribute("dataLock", m_dataLock);
e.setAttribute("skipZero", m_skipZero);
e.setAttribute("detail", kDetailLevelText[m_detailLevel]);
if (m_reportType == ePivotTable) {
e.setAttribute("type", "pivottable 1.15");
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(","));
+
+ if (m_queryColumns & eQCcapitalgain) {
+ e.setAttribute("settlementperiod", m_settlementPeriod);
+ e.setAttribute("showSTLTCapitalGains", m_showSTLTCapitalGains);
+ e.setAttribute("tseparator", m_tseparator.toString(Qt::ISODate));
+ }
} else if (m_reportType == eInfoTable) {
e.setAttribute("type", "infotable 1.0");
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 == MyMoneyTransactionFilter::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_hideTransactions = e.attribute("hidetransactions", "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_chartCHGridLines = e.attribute("chartchgridlines", "1").toUInt();
m_chartSVGridLines = e.attribute("chartsvgridlines", "1").toUInt();
m_chartByDefault = e.attribute("chartbydefault", "0").toUInt();
m_chartLineWidth = e.attribute("chartlinewidth", QString(m_lineWidth)).toUInt();
m_logYaxis = e.attribute("logYaxis", "0").toUInt();
m_dataRangeStart = e.attribute("dataRangeStart", "0");
m_dataRangeEnd = e.attribute("dataRangeEnd", "0");
m_dataMajorTick = e.attribute("dataMajorTick", "0");
m_dataMinorTick = e.attribute("dataMinorTick", "0");
m_yLabelsPrecision = e.attribute("yLabelsPrecision", "2").toUInt();
} else {
m_chartDataLabels = true;
m_chartCHGridLines = true;
m_chartSVGridLines = true;
m_chartByDefault = false;
m_chartLineWidth = 1;
m_logYaxis = false;
}
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 = MyMoneyTransactionFilter::userDefined;
}
setDateFilter(static_cast(i));
QString datalockstr = e.attribute("dataLock", "userdefined");
ok = false;
i = datalockstr.toUInt(&ok);
if (!ok) {
i = kDataLockText.indexOf(datalockstr);
if (i == -1)
i = MyMoneyReport::userDefined;
}
setDataFilter(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));
+ if (m_reportType == eQueryTable && m_queryColumns & eQCcapitalgain) {
+ m_showSTLTCapitalGains = e.attribute("showSTLTCapitalGains", "0").toUInt();
+ m_settlementPeriod = e.attribute("settlementperiod", "3").toUInt();
+ m_tseparator = QDate::fromString(e.attribute("tseparator", QDate::currentDate().addYears(-1).toString(Qt::ISODate)),Qt::ISODate);
+ }
+
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 5ef237205..462e9d0ab 100644
--- a/kmymoney/mymoney/mymoneyreport.h
+++ b/kmymoney/mymoney/mymoneyreport.h
@@ -1,791 +1,820 @@
/***************************************************************************
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, eQCcapitalgain = 0x2000, eQCend = 0x4000 };
enum EDetailLevel { eDetailNone = 0, eDetailAll, eDetailTop, eDetailGroup, eDetailTotal, eDetailEnd };
enum EChartType { eChartNone = 0, eChartLine, eChartBar, eChartPie, eChartRing, eChartStackedBar, eChartEnd };
enum dataOptionE { automatic = 0, userDefined, dataOptionCount };
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;
}
bool isHideTransactions() const {
return m_hideTransactions;
}
EChartType chartType() const {
return m_chartType;
}
bool isChartDataLabels() const {
return m_chartDataLabels;
}
bool isChartCHGridLines() const {
return m_chartCHGridLines;
}
bool isChartSVGridLines() const {
return m_chartSVGridLines;
}
bool isChartByDefault() const {
return m_chartByDefault;
}
uint chartLineWidth() const {
return m_chartLineWidth;
}
bool isLogYAxis() const {
return m_logYaxis;
}
const QString& dataRangeStart() const {
return m_dataRangeStart;
}
const QString& dataRangeEnd() const {
return m_dataRangeEnd;
}
const QString& dataMajorTick() const {
return m_dataMajorTick;
}
const QString& dataMinorTick() const {
return m_dataMinorTick;
}
uint yLabelsPrecision() const {
return m_yLabelsPrecision;
}
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 isDateUserDefined() const {
return m_dateLock == MyMoneyTransactionFilter::userDefined;
}
bool isDataUserDefined() const {
return m_dataLock == MyMoneyReport::userDefined;
}
bool isMixedTime() const {
return m_mixedTime;
}
int currentDateColumn() const {
return m_currentDateColumn;
}
+ uint settlementPeriod() const {
+ return m_settlementPeriod;
+ }
+ bool isShowingSTLTCapitalGains() const {
+ return m_showSTLTCapitalGains;
+ }
+ const QDate& termSeparator() const {
+ return m_tseparator;
+ }
/**
* @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 setHideTransactions(bool _f) {
m_hideTransactions = _f;
}
void setChartType(EChartType _type) {
m_chartType = _type;
}
void setChartDataLabels(bool _f) {
m_chartDataLabels = _f;
}
void setChartCHGridLines(bool _f) {
m_chartCHGridLines = _f;
}
void setChartSVGridLines(bool _f) {
m_chartSVGridLines = _f;
}
void setChartByDefault(bool _f) {
m_chartByDefault = _f;
}
void setChartLineWidth(uint _f) {
m_chartLineWidth = _f;
}
void setLogYAxis(bool _f) {
m_logYaxis = _f;
}
void setDataRangeStart(const QString& _f) {
m_dataRangeStart = _f;
}
void setDataRangeEnd(const QString& _f) {
m_dataRangeEnd = _f;
}
void setDataMajorTick(const QString& _f) {
m_dataMajorTick = _f;
}
void setDataMinorTick(const QString& _f) {
m_dataMinorTick = _f;
}
void setYLabelsPrecision(int _f) {
m_yLabelsPrecision = _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;
}
+ void setSettlementPeriod(uint _days) {
+ m_settlementPeriod = _days;
+ }
+ void setShowSTLTCapitalGains(bool _f) {
+ m_showSTLTCapitalGains = _f;
+ }
+ void setTermSeparator(const QDate& _date) {
+ m_tseparator = _date;
+ }
/**
* @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 != MyMoneyTransactionFilter::userDefined)
MyMoneyTransactionFilter::setDateFilter(_u);
}
void setDataFilter(dataOptionE _u) {
m_dataLock = _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 != MyMoneyTransactionFilter::userDefined) MyMoneyTransactionFilter::setDateFilter(m_dateLock);
}
MyMoneyReport::dataOptionE dataFilter() {
return m_dataLock;
}
/**
* 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 show transactions or just totals.
*/
bool m_hideTransactions;
/**
* 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_chartCHGridLines;
bool m_chartSVGridLines;
/**
* 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 Y axis is logarithmic or linear
*/
bool m_logYaxis;
/**
* Y data range
*/
QString m_dataRangeStart;
QString m_dataRangeEnd;
/**
* Y data range division
*/
QString m_dataMajorTick;
QString m_dataMinorTick;
/**
* Y labels precision
*/
uint m_yLabelsPrecision;
/**
* Whether data range should be calculated automatically or is user defined
*/
dataOptionE m_dataLock;
/**
* 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;
-
+ /**
+ * Time in days between the settlement date and the transaction date.
+ */
+ uint m_settlementPeriod;
+ /**
+ * Controls showing short-term and long-term capital gains.
+ */
+ bool m_showSTLTCapitalGains;
+ /**
+ * Date separating shot-term from long-term gains.
+ */
+ QDate m_tseparator;
/**
* 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 a6acc5d5b..90d87b6c1 100644
--- a/kmymoney/reports/listtable.cpp
+++ b/kmymoney/reports/listtable.cpp
@@ -1,613 +1,619 @@
/***************************************************************************
listtable.cpp
-------------------
begin : Sat 28 jun 2008
copyright : (C) 2004-2005 by Ace Jones
2008 by Alvaro Soliverez
(C) 2017 Łukasz Wojniłowicz
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "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;
// ****************************************************************************
//
// 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
{
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(',');
if (!m_subtotal.isEmpty() && m_subtotal.split(',').count() == 1) // constructPerformanceRow has subtotal columns already in columns
columns += m_subtotal;
QStringList postcolumns = m_postcolumns.split(',');
if (!m_postcolumns.isEmpty()) // prevent creation of empty column
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["buysST"] = i18n("Short-term Buys");
+ i18nHeaders["sellsST"] = i18n("Short-term Sells");
+ i18nHeaders["buysLT"] = i18n("Long-term Buys");
+ i18nHeaders["sellsLT"] = i18n("Long-term 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");
+ i18nHeaders["capitalgainST"] = i18n("Short-term Gain");
+ i18nHeaders["capitalgainLT"] = i18n("Long-term 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,capitalgain").split(',');
+ QStringList moneyColumns = QString("value,shares,price,latestprice,netinvvalue,buys,buysST,buysLT,sells,sellsST,sellsLT,cashincome,reinvestincome,startingbal,fees,interest,payment,balance,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,loanamount,periodicpayment,finalpayment,currentbalance,startingbal,endingbal,capitalgain,capitalgainST,capitalgainLT").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';
// initialize group names to empty, so any group will have to display its header
QStringList prevGrpNames;
for (int i = 0; i < groups.count(); ++i) {
prevGrpNames.append(QString());
}
//
// Rows
//
bool row_odd = true;
bool isLowestGroupTotal = true; // hack to inform whether to put separator line or not
// ***DV***
MyMoneyMoney startingBalance;
MyMoneyMoney balanceChange = MyMoneyMoney();
for (QList::const_iterator it_row = m_rows.begin();
it_row != m_rows.end();
++it_row) {
/** rank can be:
* 0 - opening balance
* 1 - major split of transaction
* 2 - minor split of transaction
* 3 - closing balance
* 4 - first totals row
* 5 - middle totals row
*/
const QString rowRank = (*it_row)["rank"];
// detect whether any of groups changed and display new group header in that case
for (int i = 0; i < groups.count(); ++i) {
QString curGrpName = (*it_row).value(groups.at(i));
if (curGrpName.isEmpty()) // it could be grand total
continue;
if (prevGrpNames.at(i) != curGrpName) {
// group header of lowest group doesn't bring any useful information
// if hide transaction is enabled, so don't display it
int lowestGroup = groups.count() - 1;
if (!m_config.isHideTransactions() || i != lowestGroup) {
row_odd = true;
result += "\n";
csv += "\"" + curGrpName + "\"\n";
}
if (i == lowestGroup) // lowest group has been switched...
isLowestGroupTotal = true; // ...so expect lowest group total
prevGrpNames.replace(i, curGrpName);
}
}
bool need_label = true;
QString tlink; // link information to account and transaction
if (!m_config.isHideTransactions() || rowRank == "4" || rowRank == "5") { // if hide transaction is enabled display only total rows i.e. rank = 4 || rank = 5
if (rowRank == "0" || rowRank == "3") {
// skip the opening and closing balance row,
// if the balance column is not shown
// rank = 0 for opening balance, rank = 3 for closing balance
if (!columns.contains("balance"))
continue;
result += QString("").arg((* it_row)["id"]);
// ***DV***
} else if (rowRank == "1") {
row_odd = ! row_odd;
tlink = QString("id=%1&tid=%2")
.arg((* it_row)["accountid"], (* it_row)["id"]);
result += QString("
").arg(row_odd ? "row-odd " : "row-even");
} else if (rowRank == "2")
result += QString("
").arg(row_odd ? "item1" : "item0");
else if (rowRank == "4" || rowRank == "5") {
QList::const_iterator nextRow = std::next(it_row);
if ((m_config.rowType() == MyMoneyReport::eTag)) //If we order by Tags don't show the Grand total as we can have multiple tags per transaction
continue;
else if (rowRank == "4") {
if (nextRow != m_rows.end()) {
if (isLowestGroupTotal && m_config.isHideTransactions()) {
result += QString("").arg(row_odd ? "row-odd " : "row-even");
} else
continue;
//
// Columns
//
QStringList::const_iterator it_column = columns.constBegin();
while (it_column != columns.constEnd()) {
QString data = (*it_row)[*it_column];
// ***DV***
if (rowRank == "2") {
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***
else if (rowRank == "0" || rowRank == "3") {
if (*it_column == "balance") {
data = (* it_row)["balance"];
if ((* it_row)["id"] == "A") { // opening balance?
startingBalance = MyMoneyMoney(data);
balanceChange = MyMoneyMoney();
}
}
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" && rowRank == "1") {
// Take the balance off the deepest group iterator
balanceChange += MyMoneyMoney((*it_row).value("value", "0"));
data = (balanceChange + startingBalance).toString();
} else if ((rowRank == "4" || rowRank == "5")) {
// display total title but only if first column doesn't contain any data
if (it_column == columns.constBegin() && data.isEmpty()) {
result += "";
if (rowRank == "4") {
if (!(*it_row)["depth"].isEmpty())
result += i18nc("Total balance", "Total") + ' ' + prevGrpNames.at((*it_row)["depth"].toInt());
else
result += i18n("Grand Total");
}
result.append(QLatin1Literal(" | "));
++it_column;
continue;
} else if (!m_subtotal.contains(*it_column)) { // don't display e.g. account in totals row
result.append(QLatin1Literal(" | "));
++it_column;
continue;
}
}
// 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("");
}
QString currencyID = (*it_row)["currency"];
if (currencyID.isEmpty())
currencyID = file->baseCurrency().id();
int fraction = file->currency(currencyID).smallestAccountFraction();
if (m_config.isConvertCurrency()) // don't show currency id, if there is only single currency
currencyID.clear();
if (sharesColumns.contains(*it_column)) {
if (data.isEmpty()) {
result += QString(" | ");
csv += "\"\",";
} else {
int sharesPrecision = MyMoneyMoney::denomToPrec(file->security(file->account((*it_row)["accountid"]).currencyId()).smallestAccountFraction());
result += QString("%2%1%3 | ").arg(MyMoneyMoney(data).formatMoney("", sharesPrecision), tlinkBegin, tlinkEnd);
csv += "\"" + MyMoneyMoney(data).formatMoney("", sharesPrecision, 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") {
int pricePrecision = file->security(file->account((*it_row)["accountid"]).currencyId()).pricePrecision();
result += QString("%3%2 %1%4 | ")
.arg(MyMoneyMoney(data).formatMoney(QString(), pricePrecision), currencyID, tlinkBegin, tlinkEnd);
csv += "\"" + currencyID + " " + MyMoneyMoney(data).formatMoney(QString(), pricePrecision, false) + "\",";
} else {
result += QString("%4%2 %3%5 | ")
.arg((*it_column == "value") ? " class=\"value\"" : "")
.arg(currencyID)
.arg(MyMoneyMoney(data).formatMoney(fraction))
.arg(tlinkBegin, tlinkEnd);
csv += "\"" + currencyID + " " + MyMoneyMoney(data).formatMoney(fraction, false) + "\",";
}
} else if (percentColumns.contains(*it_column)) {
if (data.isEmpty()) {
result += QString(" | ");
csv += "\"\",";
} else {
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';
}
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 dfa5aa4b5..7608bb494 100644
--- a/kmymoney/reports/querytable.cpp
+++ b/kmymoney/reports/querytable.cpp
@@ -1,1915 +1,2013 @@
/***************************************************************************
querytable.cpp
-------------------
begin : Fri Jul 23 2004
copyright : (C) 2004-2005 by Ace Jones
(C) 2007 Sascha Pfau
(C) 2017 Łukasz Wojniłowicz
***************************************************************************/
/****************************************************************************
Contains code from the func_xirr and related methods of financial.cpp
- KOffice 1.6 by Sascha Pfau. Sascha agreed to relicense those methods under
GPLv2 or later.
*****************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "querytable.h"
#include
// ----------------------------------------------------------------------------
// QT Includes
#include
#include
// ----------------------------------------------------------------------------
// KDE Includes
#include
// ----------------------------------------------------------------------------
// Project Includes
#include "mymoneyfile.h"
#include "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 = constBegin();
while (it_cash != constEnd()) {
result += (*it_cash).NPV(_rate);
++it_cash;
}
//qDebug() << "CashFlowList::NPV( " << _rate << " ) == " << result << "------------------------" << endl;
return result;
}
double CashFlowList::calculateXIRR() const
{
double resultRate = 0.00001;
double resultZero = 0.00000;
//if ( args.count() > 2 )
// resultRate = calc->conv()->asFloat ( args[2] ).asFloat();
// check pairs and count >= 2 and guess > -1.0
//if ( args[0].count() != args[1].count() || args[1].count() < 2 || resultRate <= -1.0 )
// return Value::errorVALUE();
// define max epsilon
static const double maxEpsilon = 1e-5;
// max number of iterations
static const int maxIter = 50;
// Newton's method - try to find a res, with a accuracy of maxEpsilon
double rateEpsilon, newRate, resultValue;
int i = 0;
bool contLoop;
do {
resultValue = xirrResult(resultRate);
double resultDerive = xirrResultDerive(resultRate);
//check what happens if xirrResultDerive is zero
//Don't know if it is correct to dismiss the result
if (resultDerive != 0) {
newRate = resultRate - resultValue / resultDerive;
} else {
newRate = resultRate - resultValue;
}
rateEpsilon = fabs(newRate - resultRate);
resultRate = newRate;
contLoop = (rateEpsilon > maxEpsilon) && (fabs(resultValue) > maxEpsilon);
} while (contLoop && (++i < maxIter));
if (contLoop)
return resultZero;
return resultRate;
}
double CashFlowList::xirrResult(double& rate) const
{
QDate date;
double r = rate + 1.0;
double res = 0.00000;//back().value().toDouble();
QList::const_iterator list_it = constBegin();
while (list_it != constEnd()) {
double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0;
MyMoneyMoney val = (* list_it).value();
if (e_i < 0) {
res += val.toDouble() * pow(r, -e_i);
} else {
res += val.toDouble() / pow(r, e_i);
}
++list_it;
}
return res;
}
double CashFlowList::xirrResultDerive(double& rate) const
{
QDate date;
double r = rate + 1.0;
double res = 0.00000;
QList::const_iterator list_it = constBegin();
while (list_it != constEnd()) {
double e_i = ((* list_it).today().daysTo((* list_it).date())) / 365.0;
MyMoneyMoney val = (* list_it).value();
res -= e_i * val.toDouble() / pow(r, e_i + 1.0);
++list_it;
}
return res;
}
double CashFlowList::IRR() const
{
double result = 0.0;
// set 'today', which is the most recent of all dates in the list
CashFlowListItem::setToday(mostRecent().date());
result = calculateXIRR();
return result;
}
MyMoneyMoney CashFlowList::total() const
{
MyMoneyMoney result;
const_iterator it_cash = constBegin();
while (it_cash != constEnd()) {
result += (*it_cash).value();
++it_cash;
}
return result;
}
void CashFlowList::dumpDebug() const
{
const_iterator it_item = constBegin();
while (it_item != constEnd()) {
qDebug() << (*it_item).date().toString(Qt::ISODate) << " " << (*it_item).value().toString();
++it_item;
}
}
// ****************************************************************************
//
// QueryTable implementation
//
// ****************************************************************************
/**
* TODO
*
* - Collapse 2- & 3- groups when they are identical
* - Way more test cases (especially splits & transfers)
* - Option to collapse splits
* - Option to exclude transfers
*
*/
QueryTable::QueryTable(const MyMoneyReport& _report): ListTable(_report)
{
// separated into its own method to allow debugging (setting breakpoints
// directly in ctors somehow does not work for me (ipwizard))
// TODO: remove the init() method and move the code back to the ctor
init();
}
void QueryTable::init()
{
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 + ",id,rank," + m_columns;
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,endingbal";
m_subtotal = "startingbal,buys,sells,reinvestincome,cashincome,return,returninvestment,endingbal";
}
if (qc & MyMoneyReport::eQCcapitalgain) {
m_columns += ",buys,sells,capitalgain";
m_subtotal = "buys,sells,capitalgain";
+ if (m_config.isShowingSTLTCapitalGains()) {
+ m_columns += ",buysST,sellsST,capitalgainST,buysLT,sellsLT,capitalgainLT";
+ m_subtotal += ",buysST,sellsST,capitalgainST,buysLT,sellsLT,capitalgainLT";
+ }
}
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);
constructTotalRows(); // adds total rows to m_rows
}
void QueryTable::constructTotalRows()
{
if (m_rows.isEmpty())
return;
// qSort places grand total at last position, because it doesn't belong to any group
for (int i = 0; i < m_rows.count(); ++i) {
if (m_rows.at(0)["rank"] == "4" || m_rows.at(0)["rank"] == "5") // it should be unlikely that total row is at the top of rows, so...
m_rows.move(0, m_rows.count() - 1 - i); // ...move it at the bottom
else
break;
}
MyMoneyFile* file = MyMoneyFile::instance();
QStringList subtotals = m_subtotal.split(',');
QStringList groups = m_group.split(',');
QStringList columns = m_columns.split(',');
if (!m_subtotal.isEmpty() && subtotals.count() == 1)
columns.append(m_subtotal);
QStringList postcolumns = m_postcolumns.split(',');
if (!m_postcolumns.isEmpty())
columns.append(postcolumns);
QMap>> totalCurrency;
QList> totalGroups;
QMap totalsValues;
// initialize all total values under summed columns to be zero
foreach (auto subtotal, subtotals) {
totalsValues.insert(subtotal, MyMoneyMoney());
}
totalsValues.insert("rows_count", MyMoneyMoney());
// create total groups containing totals row for each group
totalGroups.append(totalsValues); // prepend with extra group for grand total
for (int j = 0; j < groups.count(); ++j) {
totalGroups.append(totalsValues);
}
QList stashedTotalRows;
int iCurrentRow, iNextRow;
for (iCurrentRow = 0; iCurrentRow < m_rows.count();) {
iNextRow = iCurrentRow + 1;
// total rows are useless at summing so remove whole block of them at once
while (iNextRow != m_rows.count() && (m_rows.at(iNextRow)["rank"] == "4" || m_rows.at(iNextRow)["rank"] == "5")) {
stashedTotalRows.append(m_rows.takeAt(iNextRow)); // ...but stash them just in case
}
bool lastRow = (iNextRow == m_rows.count());
// sum all subtotal values for lowest group
QString currencyID = m_rows.at(iCurrentRow).value("currency");
if (m_rows.at(iCurrentRow)["rank"] == "1") { // don't sum up on balance (rank = 0 || rank = 3) and minor split (rank = 2)
foreach (auto subtotal, subtotals) {
if (!totalCurrency.contains(currencyID))
totalCurrency[currencyID].append(totalGroups);
totalCurrency[currencyID].last()[subtotal] += MyMoneyMoney(m_rows.at(iCurrentRow)[subtotal]);
}
totalCurrency[currencyID].last()["rows_count"] += MyMoneyMoney::ONE;
}
// iterate over groups from the lowest to the highest to find group change
for (int i = groups.count() - 1; i >= 0 ; --i) {
// if any of groups from next row changes (or next row is the last row), then it's time to put totals row
if (lastRow || m_rows.at(iCurrentRow)[groups.at(i)] != m_rows.at(iNextRow)[groups.at(i)]) {
bool isMainCurrencyTotal = true;
QMap>>::iterator currencyGrp = totalCurrency.begin();
while (currencyGrp != totalCurrency.end()) {
if (!MyMoneyMoney((*currencyGrp).at(i + 1).value("rows_count")).isZero()) { // if no rows summed up, then no totals row
TableRow totalsRow;
// sum all subtotal values for higher groups (excluding grand total) and reset lowest group values
QMap::iterator upperGrp = (*currencyGrp)[i].begin();
QMap::iterator lowerGrp = (*currencyGrp)[i + 1].begin();
while(upperGrp != (*currencyGrp)[i].end()) {
totalsRow[lowerGrp.key()] = lowerGrp.value().toString(); // fill totals row with subtotal values...
(*upperGrp) += (*lowerGrp);
// (*lowerGrp) = MyMoneyMoney();
++upperGrp;
++lowerGrp;
}
// custom total values calculations
foreach (auto subtotal, subtotals) {
if (subtotal == "returninvestment")
totalsRow[subtotal] = helperROI((*currencyGrp).at(i + 1).value("buys"), (*currencyGrp).at(i + 1).value("sells"),
(*currencyGrp).at(i + 1).value("startingbal"), (*currencyGrp).at(i + 1).value("endingbal"),
(*currencyGrp).at(i + 1).value("cashincome")).toString();
else if (subtotal == "price")
totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(i + 1).value("price") / (*currencyGrp).at(i + 1).value("rows_count")).toString();
}
// total values that aren't calculated here, but are taken untouched from external source, e.g. constructPerformanceRow
if (!stashedTotalRows.isEmpty()) {
for (int j = 0; j < stashedTotalRows.count(); ++j) {
if (stashedTotalRows.at(j).value("currency") != currencyID)
continue;
foreach (auto subtotal, subtotals) {
if (subtotal == "return")
totalsRow["return"] = stashedTotalRows.takeAt(j)["return"];
}
break;
}
}
(*currencyGrp).replace(i + 1, totalsValues);
for (int j = 0; j < groups.count(); ++j) {
totalsRow[groups.at(j)] = m_rows.at(iCurrentRow)[groups.at(j)]; // ...and identification
}
QString currencyID = currencyGrp.key();
if (currencyID.isEmpty() && totalCurrency.count() > 1)
currencyID = file->baseCurrency().id();
totalsRow["currency"] = currencyID;
if (isMainCurrencyTotal) {
totalsRow["rank"] = "4";
isMainCurrencyTotal = false;
} else
totalsRow["rank"] = "5";
totalsRow["depth"] = QString::number(i);
totalsRow.remove("rows_count");
m_rows.insert(iNextRow++, totalsRow); // iCurrentRow and iNextRow can diverge here by more than one
}
++currencyGrp;
}
}
}
// code to put grand total row
if (lastRow) {
bool isMainCurrencyTotal = true;
QMap>>::iterator currencyGrp = totalCurrency.begin();
while (currencyGrp != totalCurrency.end()) {
TableRow totalsRow;
QMap::const_iterator grandTotalGrp = (*currencyGrp)[0].constBegin();
while(grandTotalGrp != (*currencyGrp)[0].constEnd()) {
totalsRow[grandTotalGrp.key()] = grandTotalGrp.value().toString();
++grandTotalGrp;
}
foreach (auto subtotal, subtotals) {
if (subtotal == "returninvestment")
totalsRow[subtotal] = helperROI((*currencyGrp).at(0).value("buys"), (*currencyGrp).at(0).value("sells"),
(*currencyGrp).at(0).value("startingbal"), (*currencyGrp).at(0).value("endingbal"),
(*currencyGrp).at(0).value("cashincome")).toString();
else if (subtotal == "price")
totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(0).value("price") / (*currencyGrp).at(0).value("rows_count")).toString();
}
if (!stashedTotalRows.isEmpty()) {
for (int j = 0; j < stashedTotalRows.count(); ++j) {
foreach (auto subtotal, subtotals) {
if (subtotal == "return")
totalsRow["return"] = stashedTotalRows.takeAt(j)["return"];
}
}
}
for (int j = 0; j < groups.count(); ++j) {
totalsRow[groups.at(j)] = QString(); // no identification
}
QString currencyID = currencyGrp.key();
if (currencyID.isEmpty() && totalCurrency.count() > 1)
currencyID = file->baseCurrency().id();
totalsRow["currency"] = currencyID;
if (isMainCurrencyTotal) {
totalsRow["rank"] = "4";
isMainCurrencyTotal = false;
} else
totalsRow["rank"] = "5";
totalsRow["depth"] = "";
m_rows.append(totalsRow);
++currencyGrp;
}
break; // no use to loop further
}
iCurrentRow = iNextRow; // iCurrent makes here a leap forward by at least one
}
}
void QueryTable::constructTransactionTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
MyMoneyReport report(m_config);
report.setReportAllSplits(false);
report.setConsiderCategory(true);
bool use_transfers;
bool use_summary;
bool hide_details;
bool tag_special_case = false;
switch (m_config.rowType()) {
case MyMoneyReport::eCategory:
case MyMoneyReport::eTopCategory:
use_summary = false;
use_transfers = false;
hide_details = false;
break;
case MyMoneyReport::ePayee:
use_summary = false;
use_transfers = false;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
break;
case MyMoneyReport::eTag:
use_summary = false;
use_transfers = false;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
tag_special_case = true;
break;
default:
use_summary = true;
use_transfers = true;
hide_details = (m_config.detailLevel() == MyMoneyReport::eDetailNone);
break;
}
// support for opening and closing balances
QMap accts;
//get all transactions for this report
QList transactions = file->transactionList(report);
for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) {
TableRow qA, qS;
QDate pd;
QList tagIdListCache;
qA["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));
if (report.isConvertCurrency())
qA["currency"] = qS["currency"] = file->baseCurrency().id();
else
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.constBegin(), myBegin = splits.constEnd(); it_split != splits.constEnd(); ++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;
QString baseCurrency = file->baseCurrency().id();
QMap xrMap; // container for conversion rates from given currency to myBeginCurrency
do {
MyMoneyMoney xr;
ReportAccount splitAcc = (* it_split).accountId();
QString splitCurrency;
if (splitAcc.isInvest())
splitCurrency = file->account(file->account((*it_split).accountId()).parentAccountId()).currencyId();
else
splitCurrency = file->account((*it_split).accountId()).currencyId();
if (it_split == myBegin)
myBeginCurrency = splitCurrency;
//get fraction for account
int fraction = splitAcc.currency().smallestAccountFraction();
//use base currency fraction if not initialized
if (fraction == -1)
fraction = file->baseCurrency().smallestAccountFraction();
QString institution = splitAcc.institutionId();
QString payee = (*it_split).payeeId();
const QList tagIdList = (*it_split).tagIdList();
//convert to base currency
if (m_config.isConvertCurrency()) {
xr = xrMap.value(splitCurrency, xr); // check if there is conversion rate to myBeginCurrency already stored...
if (xr == MyMoneyMoney()) // ...if not...
xr = (*it_split).price(); // ...take conversion rate to myBeginCurrency from split
else if (splitAcc.isInvest()) // if it's stock split...
xr *= (*it_split).price(); // ...multiply it by stock price stored in split
if (myBeginCurrency != baseCurrency) { // myBeginCurrency can differ from baseCurrency...
MyMoneyPrice price = file->price(myBeginCurrency, baseCurrency,
(*it_transaction).postDate()); // ...so check conversion rate...
if (price.isValid()) {
xr *= price.rate(baseCurrency); // ...and multiply it by current price...
qA["currency"] = qS["currency"] = baseCurrency;
} else
qA["currency"] = qS["currency"] = myBeginCurrency; // ...and set information about non-baseCurrency
}
} else if (splitAcc.isInvest())
xr = (*it_split).price();
else
xr = MyMoneyMoney::ONE;
if (it_split == myBegin) {
include_me = m_config.includes(splitAcc);
if (include_me)
// track accts that will need opening and closing balances
//FIXME in some cases it will show the opening and closing
//balances but no transactions if the splits are all filtered out -- asoliverez
accts.insert(splitAcc.id(), splitAcc);
qA["account"] = splitAcc.name();
qA["accountid"] = splitAcc.id();
qA["topaccount"] = splitAcc.topParentName();
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() ? "" : shares.toString();
qA["price"] = shares.isZero() ? "" : xr.convertPrecision(splitAcc.currency().pricePrecision()).toString();
if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && shares.isNegative())
qA["action"] = "Sell";
qA["investaccount"] = splitAcc.parent().name();
MyMoneySplit stockSplit = (*it_split);
MyMoneySplit assetAccountSplit;
QList feeSplits;
QList interestSplits;
MyMoneySecurity currency;
MyMoneySecurity security;
MyMoneySplit::investTransactionTypeE transactionType;
KMyMoneyUtils::dissectTransaction((*it_transaction), stockSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
if (!(assetAccountSplit == MyMoneySplit())) {
for (it_split = splits.begin(); it_split != splits.end(); ++it_split) {
if ((*it_split) == assetAccountSplit) {
splitAcc = assetAccountSplit.accountId(); // switch over from stock split to asset split because amount in stock split doesn't take fees/interests into account
myBegin = it_split; // set myBegin to asset split, so stock split can be listed in details under splits
myBeginCurrency = (file->account((*myBegin).accountId())).currencyId();
if (m_config.isConvertCurrency()) {
if (myBeginCurrency != baseCurrency) {
MyMoneyPrice price = file->price(myBeginCurrency, baseCurrency, (*it_transaction).postDate());
if (price.isValid()) {
xr = price.rate(baseCurrency);
qA["currency"] = qS["currency"] = baseCurrency;
} else
qA["currency"] = qS["currency"] = myBeginCurrency;
} else
xr = MyMoneyMoney::ONE;
qA["price"] = shares.isZero() ? "" : (stockSplit.price() * xr / (*it_split).price()).toString();
// put conversion rate for all splits with this currency, so...
// every split of transaction have the same conversion rate
xrMap.insert(splitCurrency, MyMoneyMoney::ONE / (*it_split).price());
} else
xr = (*it_split).price();
break;
}
}
}
} else
qA["price"] = xr.toString();
a_fullname = splitAcc.fullName();
a_memo = (*it_split).memo();
transaction_text = m_config.match(&(*it_split));
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"] = '1';
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["rank"] = '1';
qA["category"] = i18n("[Split Transaction]");
qA["topcategory"] = i18nc("Split transaction", "Split");
qA["categorytype"] = i18nc("Split transaction", "Split");
m_rows += qA;
}
}
}
} else {
if (include_me) {
if (loan_special_case) {
MyMoneyMoney value = (-(* it_split).shares() * xr).convert(fraction);
if ((*it_split).action() == MyMoneySplit::ActionAmortization) {
// put the payment in the "payment" column and convert to lowest fraction
qA["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"] = '2';
} 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"] = "";
qA["rank"] = '1';
}
qA ["memo"] = (*it_split).memo();
if (report.isConvertCurrency())
qS["currency"] = file->baseCurrency().id();
else
qS["currency"] = splitAcc.currencyId();
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 &&
!(splitAcc.isInvest() && include_me)) { // otherwise stock split is displayed twice in report
if (! splitAcc.isIncomeExpense()) {
//multiply by currency and convert to lowest fraction
qS["value"] = ((*it_split).shares() * xr).convert(fraction).toString();
qS["rank"] = '1';
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
if (m_config.isConvertCurrency())
qA["currency"] = file->baseCurrency().id();
else
qA["currency"] = 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"] = "0";
qA["price"] = startPrice.convertPrecision(account.currency().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.convertPrecision(account.currency().pricePrecision()).toString();
if (account.isInvest()) {
qA["shares"] = endShares.toString();
}
qA["postdate"] = strEndDate;
qA["balance"] = endBalance.toString();
qA["rank"] = "3";
qA["id"] = 'Z';
m_rows += qA;
}
}
MyMoneyMoney QueryTable::helperROI(const MyMoneyMoney &buys, const MyMoneyMoney &sells, const MyMoneyMoney &startingBal, const MyMoneyMoney &endingBal, const MyMoneyMoney &cashIncome) const
{
MyMoneyMoney returnInvestment;
if (!buys.isZero() || !startingBal.isZero()) {
returnInvestment = (sells + buys + cashIncome + endingBal - startingBal) / (startingBal - buys);
returnInvestment = returnInvestment.convert(10000);
} else
returnInvestment = MyMoneyMoney(); // if no investment then no return on investment
return returnInvestment;
}
MyMoneyMoney QueryTable::helperIRR(const CashFlowList &all) const
{
MyMoneyMoney annualReturn;
try {
double irr = all.IRR();
#ifdef Q_CC_MSVC
annualReturn = MyMoneyMoney(_isnan(irr) ? 0 : irr, 10000);
#else
annualReturn = MyMoneyMoney(std::isnan(irr) ? 0 : irr, 10000);
#endif
} catch (QString e) {
qDebug() << e;
}
return annualReturn;
}
void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result, CashFlowList &all) 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) {
value = interestSplits.first().value() * price;
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.
all += buys;
all += sells;
all += cashincome;
all += CashFlowListItem(startingDate, -startingBal);
all += CashFlowListItem(endingDate, endingBal);
MyMoneyMoney buysTotal = buys.total();
MyMoneyMoney sellsTotal = sells.total();
MyMoneyMoney cashIncomeTotal = cashincome.total();
MyMoneyMoney reinvestIncomeTotal = reinvestincome.total();
MyMoneyMoney returnInvestment = helperROI(buysTotal, sellsTotal, startingBal, endingBal, cashIncomeTotal);
MyMoneyMoney annualReturn = helperIRR(all);
// check if there are any meaningfull values before adding them to results
if (!(buysTotal.isZero() && sellsTotal.isZero() &&
cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero() &&
startingBal.isZero() && endingBal.isZero())) {
result["return"] = annualReturn.toString();
result["returninvestment"] = returnInvestment.toString();
result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType());
result["buys"] = buysTotal.toString();
result["sells"] = sellsTotal.toString();
result["cashincome"] = cashIncomeTotal.toString();
result["reinvestincome"] = reinvestIncomeTotal.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;
+ MyMoneyMoney sellLongValue;
+ MyMoneyMoney buyLongValue;
+ MyMoneyMoney sellLongShares;
+ MyMoneyMoney buyLongShares;
+
//
// 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;
+ const bool isSTLT = report.isShowingSTLTCapitalGains();
+ const int settlementPeriod = report.settlementPeriod();
+ QDate termSeparator = report.termSeparator().addDays(-settlementPeriod);
report.validDateRange(startingDate, endingDate);
+ // Saturday and Sunday aren't valid settlement dates
+ if (endingDate.dayOfWeek() == Qt::Saturday)
+ endingDate.addDays(-1);
+ else if (endingDate.dayOfWeek() == Qt::Sunday)
+ endingDate.addDays(-2);
+
+ if (termSeparator.dayOfWeek() == Qt::Saturday)
+ termSeparator.addDays(-1);
+ else if (termSeparator.dayOfWeek() == Qt::Sunday)
+ termSeparator.addDays(-2);
+
+ if (startingDate.daysTo(endingDate) <= settlementPeriod) // no days to check for
+ return;
newStartingDate = startingDate;
- newEndingDate = endingDate;
- MyMoneyMoney endingShares = file->balance(account.id(), endingDate); // get how many shares there are over zero value
+ newEndingDate = endingDate.addDays(-settlementPeriod);
+ termSeparator = termSeparator.addDays(-settlementPeriod);
+ MyMoneyMoney endingShares = file->balance(account.id(), newEndingDate); // 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());
+ report.setDateFilter(newStartingDate, newEndingDate);
do {
QList transactions = file->transactionList(report);
for (QList::const_reverse_iterator it_t = transactions.crbegin(); it_t != transactions.crend(); ++it_t) {
MyMoneySplit shareSplit = (*it_t).splitByAccount(account.id());
MyMoneySplit assetAccountSplit;
QList feeSplits;
QList interestSplits;
MyMoneySecurity 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;
+ MyMoneyMoney tempVal = (((sellShares.abs() - buyShares)) / shares) * value;
+ buyValue += tempVal;
buyShares = sellShares.abs();
+ if (isSTLT && (*it_t).postDate() < termSeparator) {
+ buyLongValue += tempVal;
+ buyLongShares = buyShares;
+ }
} else { // add wholly sold shares
buyValue += value;
buyShares += shares;
+ if (isSTLT && (*it_t).postDate() < termSeparator) {
+ buyLongValue += value;
+ buyLongShares += 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);
+ MyMoneyMoney tempVal = ((shares - endingShares) / shares) * value;
+ MyMoneyMoney tempVal2 = (shares - endingShares);
+ buyValue += tempVal;
+ buyShares += tempVal2;
+ if (isSTLT && (*it_t).postDate() < termSeparator) {
+ buyLongValue += tempVal;
+ buyLongShares += tempVal2;
+ }
+ endingShares = MyMoneyMoney();
}
} 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;
+ buyLongShares /= 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();
+ if ((*it_t).postDate() < termSeparator)
+ buyLongShares = buyShares;
} else { // add wholly added shares
buyShares += shares;
+ if ((*it_t).postDate() < termSeparator)
+ buyLongShares += shares;
}
} else if (endingShares >= shares) { // substract not-added shares
endingShares -= shares;
} else { // substract partially not-added shares
- buyShares += (shares - endingShares);
- endingShares = MyMoneyMoney(0);
+ MyMoneyMoney tempVal = (shares - endingShares);
+ buyShares += tempVal;
+ if ((*it_t).postDate() < termSeparator)
+ buyLongShares += tempVal;
+ endingShares = MyMoneyMoney();
}
} 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());
+ // we've got buy value and no sell value of long-term shares, so get them
+ if (isSTLT && !buyLongShares.isZero()) {
+ newStartingDate = startingDate;
+ newEndingDate = endingDate.addDays(-settlementPeriod);
+ report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier
+ QList transactions = file->transactionList(report);
+ endingShares = buyLongShares;
+
+ foreach (const auto transaction, transactions) {
+ MyMoneySplit shareSplit = transaction.splitByAccount(account.id());
+ MyMoneySplit assetAccountSplit;
+ QList feeSplits;
+ QList interestSplits;
+ MyMoneySecurity currency;
+ MyMoneySplit::investTransactionTypeE transactionType;
+ KMyMoneyUtils::dissectTransaction(transaction, shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
+ if (m_config.isConvertCurrency())
+ price = account.baseCurrencyPrice(transaction.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::SellShares) {
+ if ((sellLongShares + shares).abs() >= buyLongShares) { // add partially sold long-term shares
+ sellLongValue += (sellLongShares.abs() - buyLongShares) / shares * value;
+ sellLongShares = buyLongShares;
+ break;
+ } else { // add wholly sold long-term shares
+ sellLongValue += value;
+ sellLongShares += shares;
+ }
+ } else if (transactionType == MyMoneySplit::RemoveShares) {
+ if ((sellLongShares + shares).abs() >= buyLongShares) {
+ sellLongShares = buyLongShares;
+ break;
+ } else
+ sellLongShares += shares;
+ }
+ }
+ }
+
// check if there are any meaningfull values before adding them to results
if (!(buyValue.isZero() && sellValue.isZero())) {
result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType());
result["buys"] = buyValue.toString();
result["sells"] = sellValue.toString();
result["capitalgain"] = (buyValue + sellValue).toString();
+ if (isSTLT) {
+ result["buysLT"] = buyLongValue.toString();
+ result["sellsLT"] = sellLongValue.toString();
+ result["capitalgainLT"] = (buyLongValue + sellLongValue).toString();
+ result["buysST"] = (buyValue - buyLongValue).toString();
+ result["sellsST"] = (sellValue - sellLongValue).toString();
+ result["capitalgainST"] = ((buyValue - buyLongValue) + (sellValue - sellLongValue)).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();
QMap> currencyCashFlow; // for total calculation
QList accounts;
file->accountList(accounts);
for (auto it_account = accounts.constBegin(); it_account != accounts.constEnd(); ++it_account) {
// Note, "Investment" accounts are never included in account rows because
// they don't contain anything by themselves. In reports, they are only
// useful as a "topaccount" aggregator of stock accounts
if ((*it_account).isAssetLiability() && m_config.includes((*it_account)) && (*it_account).accountType() != MyMoneyAccount::Investment) {
// don't add the account if it is closed. In fact, the business logic
// should prevent that an account can be closed with a balance not equal
// to zero, but we never know.
MyMoneyMoney shares = file->balance((*it_account).id(), m_config.toDate());
if (shares.isZero() && (*it_account).isClosed())
continue;
ReportAccount account(*it_account);
TableRow qaccountrow;
CashFlowList accountCashflow; // for total calculation
switch(m_config.queryColumns()) {
case MyMoneyReport::eQCperformance:
{
constructPerformanceRow(account, qaccountrow, accountCashflow);
if (!qaccountrow.isEmpty()) {
// assuming that that report is grouped by topaccount
qaccountrow["topaccount"] = account.topParentName();
if (m_config.isConvertCurrency())
qaccountrow["currency"] = file->baseCurrency().id();
else
qaccountrow["currency"] = account.currency().id();
if (!currencyCashFlow.value(qaccountrow.value("currency")).contains(qaccountrow.value("topaccount")))
currencyCashFlow[qaccountrow.value("currency")].insert(qaccountrow.value("topaccount"), accountCashflow); // create cashflow for unknown account...
else
currencyCashFlow[qaccountrow.value("currency")][qaccountrow.value("topaccount")] += accountCashflow; // ...or add cashflow for known account
}
break;
}
case MyMoneyReport::eQCcapitalgain:
constructCapitalGainRow(account, qaccountrow);
break;
default:
{
//get fraction for account
int fraction = account.currency().smallestAccountFraction() != -1 ?
account.currency().smallestAccountFraction() : file->baseCurrency().smallestAccountFraction();
MyMoneyMoney netprice = account.deepCurrencyPrice(m_config.toDate());
if (m_config.isConvertCurrency() && account.isForeignCurrency())
netprice *= account.baseCurrencyPrice(m_config.toDate()); // display currency is base currency, so set the price
netprice = netprice.reduce();
shares = shares.reduce();
qaccountrow["price"] = netprice.convertPrecision(account.currency().pricePrecision()).toString();
qaccountrow["value"] = (netprice * shares).convert(fraction).toString();
qaccountrow["shares"] = shares.toString();
QString iid = account.institutionId();
// If an account does not have an institution, get it from the top-parent.
if (iid.isEmpty() && !account.isTopLevel())
iid = account.topParent().institutionId();
if (iid.isEmpty())
qaccountrow["institution"] = i18nc("No institution", "None");
else
qaccountrow["institution"] = file->institution(iid).name();
qaccountrow["type"] = KMyMoneyUtils::accountTypeToString(account.accountType());
}
}
if (qaccountrow.isEmpty()) // don't add the account if there are no calculated values
continue;
qaccountrow["rank"] = '1';
qaccountrow["account"] = account.name();
qaccountrow["accountid"] = account.id();
qaccountrow["topaccount"] = account.topParentName();
if (m_config.isConvertCurrency())
qaccountrow["currency"] = file->baseCurrency().id();
else
qaccountrow["currency"] = account.currency().id();
m_rows.append(qaccountrow);
}
}
if (m_config.queryColumns() == MyMoneyReport::eQCperformance) {
TableRow qtotalsrow;
qtotalsrow["rank"] = "4"; // add identification of row as total
QMap currencyGrandCashFlow;
QMap>::iterator currencyAccGrp = currencyCashFlow.begin();
while (currencyAccGrp != currencyCashFlow.end()) {
// convert map of top accounts with cashflows to TableRow
for (QMap::iterator topAccount = (*currencyAccGrp).begin(); topAccount != (*currencyAccGrp).end(); ++topAccount) {
qtotalsrow["topaccount"] = topAccount.key();
qtotalsrow["return"] = helperIRR(topAccount.value()).toString();
qtotalsrow["currency"] = currencyAccGrp.key();
currencyGrandCashFlow[currencyAccGrp.key()] += topAccount.value(); // cumulative sum of cashflows of each topaccount
m_rows.append(qtotalsrow); // rows aren't sorted yet, so no problem with adding them randomly at the end
}
++currencyAccGrp;
}
QMap::iterator currencyGrp = currencyGrandCashFlow.begin();
qtotalsrow["topaccount"] = ""; // empty topaccount because it's grand cashflow
while (currencyGrp != currencyGrandCashFlow.end()) {
qtotalsrow["return"] = helperIRR(currencyGrp.value()).toString();
qtotalsrow["currency"] = currencyGrp.key();
m_rows.append(qtotalsrow);
++currencyGrp;
}
}
}
void QueryTable::constructSplitsTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
//make sure we have all subaccounts of investment accounts
includeInvestmentSubAccounts();
MyMoneyReport report(m_config);
report.setReportAllSplits(false);
report.setConsiderCategory(true);
// support for opening and closing balances
QMap accts;
//get all transactions for this report
QList transactions = file->transactionList(report);
for (QList::const_iterator it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) {
TableRow qA, qS;
QDate pd;
qA["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));
if (report.isConvertCurrency())
qA["currency"] = qS["currency"] = file->baseCurrency().id();
else
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.constBegin(), myBegin = splits.constEnd(); it_split != splits.constEnd(); ++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.convertPrecision(splitAcc.currency().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.convertPrecision(splitAcc.currency().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"] = '1';
//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.constBegin();
//there are supposed to be only 2 splits if we ever get here
if (tempSplit == myBegin && splits.count() > 1)
++tempSplit;
//show the name of the category, or "transfer to/from" if it as an account
ReportAccount tempSplitAcc = (*tempSplit).accountId();
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
if (m_config.isConvertCurrency())
qA["currency"] = file->baseCurrency().id();
else
qA["currency"] = 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"] = "0";
qA["price"] = startPrice.convertPrecision(account.currency().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;
qA["rank"] = "3";
//ending balance
qA["price"] = endPrice.convertPrecision(account.currency().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/widgets/CMakeLists.txt b/kmymoney/widgets/CMakeLists.txt
index 9934fd438..25ffe1a8c 100644
--- a/kmymoney/widgets/CMakeLists.txt
+++ b/kmymoney/widgets/CMakeLists.txt
@@ -1,198 +1,199 @@
########### create links ###############
set(kmymoney_STAT_HEADERS
kaccounttemplateselector.h kbudgetvalues.h kguiutils.h
kmymoneyaccountcombo.h kmymoneyaccountcompletion.h
kmymoneyaccountselector.h
kmymoneycategory.h
kmymoneycombo.h kmymoneymvccombo.h kmymoneycompletion.h
kmymoneycurrencyselector.h kmymoneydateinput.h kmymoneyedit.h
kmymoneylineedit.h kmymoneyselector.h
kmymoneytitlelabel.h kmymoneywizard.h register.h registeritem.h
scheduledtransaction.h selectedtransaction.h stdtransactiondownloaded.h
stdtransactionmatched.h transactioneditorcontainer.h
transactionform.h transaction.h transactionsortoptionimpl.h
reporttabimpl.h reportcontrolimpl.h
kmymoneyvalidationfeedback.h
onlinejobmessagesview.h
kmymoneydateedit.h
amountedit.h
)
########### Shared widget library ###########
set(kmm_widgets_sources
kmymoneydateinput.cpp
kmymoneyvalidationfeedback.cpp
styleditemdelegateforwarder.cpp
kmymoneyedit.cpp
kmymoneylineedit.cpp
kmymoneytextedit.cpp
kmymoneymvccombo.cpp
kmymoneyselector.cpp
kmymoneycalculator.cpp
ktreewidgetfilterlinewidget.cpp
kguiutils.cpp
onlinejobmessagesview.cpp
kmymoneydateedit.cpp
amountedit.cpp
)
ki18n_wrap_ui(kmm_widgets_sources
kmymoneyvalidationfeedback.ui
onlinejobmessagesview.ui
)
add_library(kmm_widgets SHARED ${kmm_widgets_sources})
target_link_libraries(kmm_widgets PUBLIC
KF5::TextWidgets
KF5::KIOWidgets
KF5::Completion
KF5::Notifications
KF5::ItemViews
KF5::I18n
Qt5::Gui
Qt5::Sql
Qt5::Core
Alkimia::alkimia
kmm_config
kmm_mymoney
)
set_target_properties(kmm_widgets PROPERTIES
VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}
COMPILE_FLAGS "-DKMM_BUILD_WIDGETS_LIB"
)
generate_export_header(kmm_widgets)
install(TARGETS kmm_widgets ${INSTALL_TARGETS_DEFAULT_ARGS} )
########### Basic Widget Library (kmymoney_base) STATIC #################
# Common sources for libkmymoney.so and libwidgets.a that do not
# contain the KMM_DESIGNER flag
set(_uncritial_common_sources
fixedcolumntreeview.cpp
kbudgetvalues.cpp
kmymoneyaccountcombo.cpp
kmymoneyaccountselector.cpp
kmymoneyaccounttreeview.cpp
kmymoneycombo.cpp
kmymoneycompletion.cpp
kmymoneytitlelabel.cpp
kmymoneydateedit.cpp
kmymoneywizard.cpp
kpricetreeitem.cpp
registeritem.cpp
scheduledtransaction.cpp
selectedtransaction.cpp
stdtransactiondownloaded.cpp
stdtransactionmatched.cpp
transactionform.cpp
transactionsortoptionimpl.cpp
)
# sources that contain the KMM_DESIGNER flag
set (_critial_common_sources
kaccounttemplateselector.cpp
kmymoneycurrencyselector.cpp
kmymoneyaccountcompletion.cpp
kmymoneycategory.cpp
register.cpp
transaction.cpp
transactioneditorcontainer.cpp
)
set (kmymoney_base_UI
kbudgetvaluesdecl.ui transactionsortoptiondecl.ui kaccounttemplateselectordecl.ui
)
ki18n_wrap_ui(kmymoney_base_ui_srcs ${kmymoney_base_UI})
set(_uncritial_common_sources ${_uncritial_common_sources}
${kmymoney_base_ui_srcs})
# in order to use add_dependencies, we need to add this custom target
# for all generated header files.
# (see http://www.vtk.org/Wiki/CMake_FAQ#How_can_I_add_a_dependency_to_a_source_file_which_is_generated_in_a_subdirectory.3F )
add_custom_target(generate_base_ui_srcs DEPENDS
${kmymoney_base_ui_srcs})
# We can compile the uncritical sources without KMM_DESIGNER flags
add_library(kmymoney_base STATIC ${_uncritial_common_sources})
# TODO: fix dependencies
target_link_libraries(kmymoney_base KF5::XmlGui KF5::TextWidgets KF5::IconThemes KF5::I18n KF5::ConfigWidgets KF5::ConfigCore KF5::Completion Qt5::Gui Qt5::Widgets Qt5::Sql Qt5::Xml Alkimia::alkimia)
add_dependencies(kmymoney_base kmm_config)
########### QtDesigner Widget Library (kmymoneywidgets) #################
# we never link against this library,
# but it is needed for uic and QtDesigner
if( USE_QT_DESIGNER )
set(kmymoneywidgets_PART_SRCS
${CMAKE_CURRENT_BINARY_DIR}/kmymoneywidgets.cpp)
kde4_add_widget_files(kmymoneywidgets_PART_SRCS kmymoney.widgets)
set(kmymoneywidgets_PART_SRCS
${_critial_common_sources}
${kmymoneywidgets_PART_SRCS})
add_library(kmymoneywidgets MODULE ${kmymoneywidgets_PART_SRCS})
include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} )
# The option -DKMM_DESIGNER will leave away any code that breaks uic.
set_target_properties(kmymoneywidgets PROPERTIES
COMPILE_FLAGS "-DKMM_DESIGNER")
# The qt-designer widget library shouldn't need to link against the
# dialogs and converter libraries. If a widget references something
# from one of these libraries it is most likely due to code that needs
# to be excluded with a KMM_DESIGNER ifndef.
target_link_libraries(kmymoneywidgets kmymoney_base kmm_mymoney kmymoney_common kmm_config models)
install(TARGETS kmymoneywidgets DESTINATION ${QT_PLUGINS_DIR}/designer )
endif( USE_QT_DESIGNER )
########### Widget Library (widgets) STATIC #################
set(libwidgets_a_SOURCES
${_critial_common_sources}
kmymoneybriefschedule.cpp
registersearchline.cpp
transactioneditorcontainer.cpp
reporttabimpl.cpp
reportcontrolimpl.cpp
daterangedlg.cpp
)
set(libwidgets_a_UI
kschedulebriefwidget.ui reportcontrol.ui
reporttabgeneral.ui
reporttabrowcolquery.ui reporttabrowcolpivot.ui
reporttabrange.ui reporttabchart.ui
+ reporttabcapitalgain.ui
daterangedlgdecl.ui
)
# using uic on the above UI files DEPENDS on libkmymoney.so. If uic
# does not find libkmymoney.so, gcc will fail compiling
# kmymoneyreportconfigtab2decl.cpp and throw errors like "invalid use
# of undefined type `struct KMyMoneyGeneralCombo'"
ki18n_wrap_ui(widgets_ui_srcs ${libwidgets_a_UI})
add_custom_target(generate_widgets_ui_srcs DEPENDS ${widgets_ui_srcs})
add_library(widgets STATIC
${libwidgets_a_SOURCES}
${widgets_ui_srcs}
)
target_link_libraries(widgets KF5::XmlGui kmymoney_base)
add_dependencies(widgets kmm_config)
########### install files ###############
install(FILES ${kmymoney_STAT_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kmm_widgets_export.h
DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel)
diff --git a/kmymoney/widgets/reporttabcapitalgain.ui b/kmymoney/widgets/reporttabcapitalgain.ui
new file mode 100644
index 000000000..e4bc4d277
--- /dev/null
+++ b/kmymoney/widgets/reporttabcapitalgain.ui
@@ -0,0 +1,112 @@
+
+
+ ReportTabCapitalGain
+
+
+
+ 0
+ 0
+ 441
+ 102
+
+
+
+ Report Tab
+
+
+
+
+
+ <p>On this tab, you set the basic properties of this report.</p>
+
+
+ -
+
+
-
+
+
+ Time in days between the settlement date and the transaction date.
+
+
+ 30
+
+
+ 3
+
+
+
+ -
+
+
+ Settlement period
+
+
+
+ -
+
+
+ Before this date investments are counted as long-term investments.
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Terms separator
+
+
+
+ -
+
+
+ -
+
+
+ Show short-term and long-term capital gains
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Expanding
+
+
+
+ 20
+ 20
+
+
+
+
+
+
+
+
+
+ kMyMoneyDateInput
+ QWidget
+
+
+
+
+
+
diff --git a/kmymoney/widgets/reporttabimpl.cpp b/kmymoney/widgets/reporttabimpl.cpp
index c9873b15b..9bdec8805 100644
--- a/kmymoney/widgets/reporttabimpl.cpp
+++ b/kmymoney/widgets/reporttabimpl.cpp
@@ -1,257 +1,271 @@
/* This file is part of the KDE project
Copyright (C) 2009 Laurent Montel
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "reporttabimpl.h"
#include "kmymoneyglobalsettings.h"
#include
#include
#include "ui_reporttabgeneral.h"
#include "ui_reporttabrowcolpivot.h"
#include "ui_reporttabrowcolquery.h"
#include "ui_reporttabchart.h"
#include "ui_reporttabrange.h"
+#include "ui_reporttabcapitalgain.h"
#include
#include "mymoney/mymoneyreport.h"
ReportTabGeneral::ReportTabGeneral(QWidget *parent)
: QWidget(parent)
{
ui = new Ui::ReportTabGeneral;
ui->setupUi(this);
}
ReportTabGeneral::~ReportTabGeneral()
{
delete ui;
}
ReportTabRowColPivot::ReportTabRowColPivot(QWidget *parent)
: QWidget(parent)
{
ui = new Ui::ReportTabRowColPivot;
ui->setupUi(this);
}
ReportTabRowColPivot::~ReportTabRowColPivot()
{
delete ui;
}
ReportTabRowColQuery::ReportTabRowColQuery(QWidget *parent)
: QWidget(parent)
{
ui = new Ui::ReportTabRowColQuery;
ui->setupUi(this);
ui->buttonGroup1->setExclusive(false);
ui->buttonGroup1->setId(ui->m_checkMemo, 0);
ui->buttonGroup1->setId(ui->m_checkShares, 1);
ui->buttonGroup1->setId(ui->m_checkPrice, 2);
ui->buttonGroup1->setId(ui->m_checkReconciled, 3);
ui->buttonGroup1->setId(ui->m_checkAccount, 4);
ui->buttonGroup1->setId(ui->m_checkNumber, 5);
ui->buttonGroup1->setId(ui->m_checkPayee, 6);
ui->buttonGroup1->setId(ui->m_checkCategory, 7);
ui->buttonGroup1->setId(ui->m_checkAction, 8);
ui->buttonGroup1->setId(ui->m_checkBalance, 9);
connect(ui->m_checkHideTransactions, SIGNAL(toggled(bool)), this, SLOT(slotHideTransactionsChanged(bool)));
}
void ReportTabRowColQuery::slotHideTransactionsChanged(bool checked)
{
if (checked) // toggle m_checkHideSplitDetails only if it's mandatory
ui->m_checkHideSplitDetails->setChecked(checked);
ui->m_checkHideSplitDetails->setEnabled(!checked); // hiding transactions without hiding splits isn't allowed
}
ReportTabRowColQuery::~ReportTabRowColQuery()
{
delete ui;
}
ReportTabChart::ReportTabChart(QWidget *parent)
: QWidget(parent)
{
ui = new Ui::ReportTabChart;
ui->setupUi(this);
ui->m_comboType->addItem(i18nc("type of graphic chart", "Line"), MyMoneyReport::eChartLine);
ui->m_comboType->addItem(i18nc("type of graphic chart", "Bar"), MyMoneyReport::eChartBar);
ui->m_comboType->addItem(i18nc("type of graphic chart", "Stacked Bar"), MyMoneyReport::eChartStackedBar);
ui->m_comboType->addItem(i18nc("type of graphic chart", "Pie"), MyMoneyReport::eChartPie);
ui->m_comboType->addItem(i18nc("type of graphic chart", "Ring"), MyMoneyReport::eChartRing);
connect(ui->m_comboType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChartTypeChanged(int)));
emit ui->m_comboType->currentIndexChanged(ui->m_comboType->currentIndex());
}
ReportTabChart::~ReportTabChart()
{
delete ui;
}
void ReportTabChart::slotChartTypeChanged(int index)
{
if (index == MyMoneyReport::EChartType::eChartPie ||
index == MyMoneyReport::EChartType::eChartRing) {
ui->m_checkCHGridLines->setText(i18n("Show circular grid lines"));
ui->m_checkSVGridLines->setText(i18n("Show sagittal grid lines"));
ui->m_logYaxis->setChecked(false);
ui->m_logYaxis->setEnabled(false);
} else {
ui->m_checkCHGridLines->setText(i18n("Show horizontal grid lines"));
ui->m_checkSVGridLines->setText(i18n("Show vertical grid lines"));
ui->m_logYaxis->setEnabled(true);
}
}
ReportTabRange::ReportTabRange(QWidget *parent)
: QWidget(parent)
{
ui = new Ui::ReportTabRange;
ui->setupUi(this);
m_dateRange = new DateRangeDlg(this->parentWidget());
ui->dateRangeGrid->addWidget(m_dateRange, 0, 0, 1, 2);
connect(ui->m_yLabelsPrecision, SIGNAL(valueChanged(int)), this, SLOT(slotYLabelsPrecisionChanged(int)));
emit ui->m_yLabelsPrecision->valueChanged(ui->m_yLabelsPrecision->value());
connect(ui->m_dataRangeStart, SIGNAL(editingFinished()), this, SLOT(slotEditingFinishedStart()));
connect(ui->m_dataRangeEnd, SIGNAL(editingFinished()), this, SLOT(slotEditingFinishedEnd()));
connect(ui->m_dataMajorTick, SIGNAL(editingFinished()), this, SLOT(slotEditingFinishedMajor()));
connect(ui->m_dataMinorTick, SIGNAL(editingFinished()), this, SLOT(slotEditingFinishedMinor()));
connect(ui->m_dataLock, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDataLockChanged(int)));
emit ui->m_dataLock->currentIndexChanged(ui->m_dataLock->currentIndex());
}
ReportTabRange::~ReportTabRange()
{
delete ui;
}
void ReportTabRange::setRangeLogarythmic(bool set)
{
// major and minor tick have no influence if axis is logarithmic so hide them
if (set) {
ui->lblDataMajorTick->hide();
ui->lblDataMinorTick->hide();
ui->m_dataMajorTick->hide();
ui->m_dataMinorTick->hide();
} else {
ui->lblDataMajorTick->show();
ui->lblDataMinorTick->show();
ui->m_dataMajorTick->show();
ui->m_dataMinorTick->show();
}
}
void ReportTabRange::slotEditingFinished(EDimension dim)
{
qreal dataRangeStart = locale().toDouble(ui->m_dataRangeStart->text());
qreal dataRangeEnd = locale().toDouble(ui->m_dataRangeEnd->text());
qreal dataMajorTick = locale().toDouble(ui->m_dataMajorTick->text());
qreal dataMinorTick = locale().toDouble(ui->m_dataMinorTick->text());
if (dataRangeEnd < dataRangeStart) { // end must be higher than start
if (dim == eRangeEnd) {
ui->m_dataRangeStart->setText(ui->m_dataRangeEnd->text());
dataRangeStart = dataRangeEnd;
} else {
ui->m_dataRangeEnd->setText(ui->m_dataRangeStart->text());
dataRangeEnd = dataRangeStart;
}
}
if ((dataRangeStart != 0 || dataRangeEnd != 0)) { // if data range isn't going to be reset
if ((dataRangeEnd - dataRangeStart) < dataMajorTick) // major tick cannot be greater than data range
dataMajorTick = dataRangeEnd - dataRangeStart;
if (dataMajorTick != 0 && // if major tick isn't going to be reset
dataMajorTick < (dataRangeEnd - dataRangeStart) * 0.01) // constraint major tick to be greater or equal to 1% of data range
dataMajorTick = (dataRangeEnd - dataRangeStart) * 0.01; // that should produce more than 256 Y labels in KReportChartView::slotNeedUpdate
//set precision of major tick to be greater by 1
ui->m_dataMajorTick->setText(locale().toString(dataMajorTick, 'f', ui->m_yLabelsPrecision->value() + 1).remove(locale().groupSeparator()).remove(QRegularExpression("0+$")).remove(QRegularExpression("\\" + locale().decimalPoint() + "$")));
}
if (dataMajorTick < dataMinorTick) { // major tick must be higher than minor
if (dim == eMinorTick) {
ui->m_dataMajorTick->setText(ui->m_dataMinorTick->text());
dataMajorTick = dataMinorTick;
} else {
ui->m_dataMinorTick->setText(ui->m_dataMajorTick->text());
dataMinorTick = dataMajorTick;
}
}
if (dataMinorTick < dataMajorTick * 0.1) { // constraint minor tick to be greater or equal to 10% of major tick, and set precision to be greater by 1
dataMinorTick = dataMajorTick * 0.1;
ui->m_dataMinorTick->setText(locale().toString(dataMinorTick, 'f', ui->m_yLabelsPrecision->value() + 1).remove(locale().groupSeparator()).remove(QRegularExpression("0+$")).remove(QRegularExpression("\\" + locale().decimalPoint() + "$")));
}
}
void ReportTabRange::slotEditingFinishedStart()
{
slotEditingFinished(eRangeStart);
}
void ReportTabRange::slotEditingFinishedEnd()
{
slotEditingFinished(eRangeEnd);
}
void ReportTabRange::slotEditingFinishedMajor()
{
slotEditingFinished(eMajorTick);
}
void ReportTabRange::slotEditingFinishedMinor()
{
slotEditingFinished(eMinorTick);
}
void ReportTabRange::slotYLabelsPrecisionChanged(const int& value)
{
ui->m_dataRangeStart->setValidator(0);
ui->m_dataRangeEnd->setValidator(0);
ui->m_dataMajorTick->setValidator(0);
ui->m_dataMinorTick->setValidator(0);
MyDoubleValidator *dblVal = new MyDoubleValidator(value);
ui->m_dataRangeStart->setValidator(dblVal);
ui->m_dataRangeEnd->setValidator(dblVal);
MyDoubleValidator *dblVal2 = new MyDoubleValidator(value + 1);
ui->m_dataMajorTick->setValidator(dblVal2);
ui->m_dataMinorTick->setValidator(dblVal2);
}
void ReportTabRange::slotDataLockChanged(int index) {
if (index == MyMoneyReport::dataOptionE::automatic) {
ui->m_dataRangeStart->setText(QStringLiteral("0"));
ui->m_dataRangeEnd->setText(QStringLiteral("0"));
ui->m_dataMajorTick->setText(QStringLiteral("0"));
ui->m_dataMinorTick->setText(QStringLiteral("0"));
ui->m_dataRangeStart->setEnabled(false);
ui->m_dataRangeEnd->setEnabled(false);
ui->m_dataMajorTick->setEnabled(false);
ui->m_dataMinorTick->setEnabled(false);
} else {
ui->m_dataRangeStart->setEnabled(true);
ui->m_dataRangeEnd->setEnabled(true);
ui->m_dataMajorTick->setEnabled(true);
ui->m_dataMinorTick->setEnabled(true);
}
}
+
+ReportTabCapitalGain::ReportTabCapitalGain(QWidget *parent)
+ : QWidget(parent)
+{
+ ui = new Ui::ReportTabCapitalGain;
+ ui->setupUi(this);
+}
+
+ReportTabCapitalGain::~ReportTabCapitalGain()
+{
+ delete ui;
+}
+
diff --git a/kmymoney/widgets/reporttabimpl.h b/kmymoney/widgets/reporttabimpl.h
index 27d71d4a1..f6718a103 100644
--- a/kmymoney/widgets/reporttabimpl.h
+++ b/kmymoney/widgets/reporttabimpl.h
@@ -1,132 +1,141 @@
/* This file is part of the KDE project
Copyright (C) 2009 Laurent Montel
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef REPORTTABIMPL_H
#define REPORTTABIMPL_H
#include
#include
class DateRangeDlg;
namespace Ui
{
class ReportTabGeneral;
class ReportTabRowColPivot;
class ReportTabRowColQuery;
class ReportTabChart;
class ReportTabRange;
+class ReportTabCapitalGain;
}
class ReportTabGeneral : public QWidget
{
public:
ReportTabGeneral(QWidget *parent);
virtual ~ReportTabGeneral();
Ui::ReportTabGeneral* ui;
};
class ReportTabRowColPivot : public QWidget
{
public:
ReportTabRowColPivot(QWidget *parent);
virtual ~ReportTabRowColPivot();
Ui::ReportTabRowColPivot* ui;
};
class ReportTabRowColQuery : public QWidget
{
Q_OBJECT
public:
ReportTabRowColQuery(QWidget *parent);
virtual ~ReportTabRowColQuery();
Ui::ReportTabRowColQuery* ui;
private slots:
void slotHideTransactionsChanged(bool checked);
};
class ReportTabChart : public QWidget
{
Q_OBJECT
public:
ReportTabChart(QWidget *parent);
virtual ~ReportTabChart();
Ui::ReportTabChart* ui;
private slots:
void slotChartTypeChanged(int index);
};
class ReportTabRange : public QWidget
{
Q_OBJECT
public:
ReportTabRange(QWidget *parent);
virtual ~ReportTabRange();
Ui::ReportTabRange* ui;
DateRangeDlg *m_dateRange;
void setRangeLogarythmic(bool set);
private:
enum EDimension { eRangeStart = 0, eRangeEnd, eMajorTick, eMinorTick};
private slots:
void slotEditingFinished(EDimension dim);
void slotEditingFinishedStart();
void slotEditingFinishedEnd();
void slotEditingFinishedMajor();
void slotEditingFinishedMinor();
void slotYLabelsPrecisionChanged(const int &value);
void slotDataLockChanged(int index);
};
+class ReportTabCapitalGain : public QWidget
+{
+public:
+ ReportTabCapitalGain(QWidget *parent);
+ virtual ~ReportTabCapitalGain();
+ Ui::ReportTabCapitalGain* ui;
+};
+
class MyDoubleValidator : public QDoubleValidator
{
public:
MyDoubleValidator(int decimals, QObject * parent = 0) :
QDoubleValidator(0, 0, decimals, parent)
{
}
QValidator::State validate(QString &s, int &i) const
{
Q_UNUSED(i);
if (s.isEmpty() || s == "-") {
return QValidator::Intermediate;
}
QChar decimalPoint = locale().decimalPoint();
if(s.indexOf(decimalPoint) != -1) {
int charsAfterPoint = s.length() - s.indexOf(decimalPoint) - 1;
if (charsAfterPoint > decimals()) {
return QValidator::Invalid;
}
}
bool ok;
locale().toDouble(s, &ok);
if (ok) {
return QValidator::Acceptable;
} else {
return QValidator::Invalid;
}
}
};
#endif /* REPORTTABIMPL_H */