diff --git a/kmymoney/dialogs/kreportconfigurationfilterdlg.h b/kmymoney/dialogs/kreportconfigurationfilterdlg.h --- a/kmymoney/dialogs/kreportconfigurationfilterdlg.h +++ b/kmymoney/dialogs/kreportconfigurationfilterdlg.h @@ -68,6 +68,7 @@ QPointer m_tabRowColQuery; QPointer m_tabChart; QPointer m_tabRange; + QPointer m_tabCapitalGain; MyMoneyReport m_initialState; MyMoneyReport m_currentState; diff --git a/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp b/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp --- a/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp +++ b/kmymoney/dialogs/kreportconfigurationfilterdlg.cpp @@ -67,6 +67,7 @@ #include #include #include +#include KReportConfigurationFilterDlg::KReportConfigurationFilterDlg( MyMoneyReport report, QWidget *parent) @@ -135,6 +136,10 @@ 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)); @@ -278,6 +283,11 @@ 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); } @@ -515,6 +525,12 @@ } } + 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 // diff --git a/kmymoney/mymoney/mymoneyreport.h b/kmymoney/mymoney/mymoneyreport.h --- a/kmymoney/mymoney/mymoneyreport.h +++ b/kmymoney/mymoney/mymoneyreport.h @@ -227,6 +227,15 @@ 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 */ @@ -347,6 +356,15 @@ 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 */ @@ -749,7 +767,18 @@ * 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() diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp --- a/kmymoney/mymoney/mymoneyreport.cpp +++ b/kmymoney/mymoney/mymoneyreport.cpp @@ -84,6 +84,9 @@ 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; @@ -133,6 +136,9 @@ 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 @@ -407,6 +413,12 @@ 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); @@ -752,6 +764,12 @@ } 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(); diff --git a/kmymoney/reports/listtable.cpp b/kmymoney/reports/listtable.cpp --- a/kmymoney/reports/listtable.cpp +++ b/kmymoney/reports/listtable.cpp @@ -169,6 +169,10 @@ 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"); @@ -201,9 +205,11 @@ 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 diff --git a/kmymoney/reports/querytable.cpp b/kmymoney/reports/querytable.cpp --- a/kmymoney/reports/querytable.cpp +++ b/kmymoney/reports/querytable.cpp @@ -381,6 +381,10 @@ 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"; @@ -1341,6 +1345,11 @@ MyMoneyMoney sellShares; MyMoneyMoney buyShares; + MyMoneyMoney sellLongValue; + MyMoneyMoney buyLongValue; + MyMoneyMoney sellLongShares; + MyMoneyMoney buyLongShares; + // // Calculate capital gain // @@ -1353,16 +1362,34 @@ 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); @@ -1380,44 +1407,66 @@ 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; @@ -1429,12 +1478,61 @@ 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 } diff --git a/kmymoney/widgets/CMakeLists.txt b/kmymoney/widgets/CMakeLists.txt --- a/kmymoney/widgets/CMakeLists.txt +++ b/kmymoney/widgets/CMakeLists.txt @@ -172,6 +172,7 @@ reporttabgeneral.ui reporttabrowcolquery.ui reporttabrowcolpivot.ui reporttabrange.ui reporttabchart.ui + reporttabcapitalgain.ui daterangedlgdecl.ui ) diff --git a/kmymoney/widgets/reporttabcapitalgain.ui b/kmymoney/widgets/reporttabcapitalgain.ui new file mode 100644 --- /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 +
kmymoneydateinput.h
+
+
+ + +
diff --git a/kmymoney/widgets/reporttabimpl.h b/kmymoney/widgets/reporttabimpl.h --- a/kmymoney/widgets/reporttabimpl.h +++ b/kmymoney/widgets/reporttabimpl.h @@ -32,6 +32,7 @@ class ReportTabRowColQuery; class ReportTabChart; class ReportTabRange; +class ReportTabCapitalGain; } class ReportTabGeneral : public QWidget @@ -93,6 +94,14 @@ void slotDataLockChanged(int index); }; +class ReportTabCapitalGain : public QWidget +{ +public: + ReportTabCapitalGain(QWidget *parent); + virtual ~ReportTabCapitalGain(); + Ui::ReportTabCapitalGain* ui; +}; + class MyDoubleValidator : public QDoubleValidator { public: diff --git a/kmymoney/widgets/reporttabimpl.cpp b/kmymoney/widgets/reporttabimpl.cpp --- a/kmymoney/widgets/reporttabimpl.cpp +++ b/kmymoney/widgets/reporttabimpl.cpp @@ -28,6 +28,7 @@ #include "ui_reporttabrowcolquery.h" #include "ui_reporttabchart.h" #include "ui_reporttabrange.h" +#include "ui_reporttabcapitalgain.h" #include #include "mymoney/mymoneyreport.h" @@ -255,3 +256,16 @@ ui->m_dataMinorTick->setEnabled(true); } } + +ReportTabCapitalGain::ReportTabCapitalGain(QWidget *parent) + : QWidget(parent) +{ + ui = new Ui::ReportTabCapitalGain; + ui->setupUi(this); +} + +ReportTabCapitalGain::~ReportTabCapitalGain() +{ + delete ui; +} +