Commit d56fe898 authored by Łukasz Wojniłowicz's avatar Łukasz Wojniłowicz
Browse files

Add Capital Gains report



REVIEW: 129790
BUG: 250471
Signed-off-by: Łukasz Wojniłowicz's avatarŁukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
parent 42058f8d
......@@ -36,7 +36,7 @@ const QStringList MyMoneyReport::kColumnTypeText = QString("none,months,bimonths
// if you add names here, don't forget to update the bitmap for EQueryColumns
// and shift the bit for eQCend one position to the left
const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance").split(',');
const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance,capitalgain").split(',');
const MyMoneyReport::EReportType MyMoneyReport::kTypeArray[] = { eNoReport, ePivotTable, ePivotTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, ePivotTable, ePivotTable, eInfoTable, eInfoTable, eInfoTable, eQueryTable, eQueryTable, eNoReport };
const QStringList MyMoneyReport::kDetailLevelText = QString("none,all,top,group,total,invalid").split(',');
......
......@@ -62,7 +62,7 @@ public:
// if you add bits to this bitmask, start with the value currently assigned to eQCend and update its value afterwards
// also don't forget to add column names to kQueryColumnsText in mymoneyreport.cpp
enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCtag = 0x8, eQCmemo = 0x10, eQCaccount = 0x20, eQCreconciled = 0x40, eQCaction = 0x80, eQCshares = 0x100, eQCprice = 0x200, eQCperformance = 0x400, eQCloan = 0x800, eQCbalance = 0x1000, eQCend = 0x2000 };
enum EQueryColumns { eQCnone = 0x0, eQCbegin = 0x1, eQCnumber = 0x1, eQCpayee = 0x2, eQCcategory = 0x4, eQCtag = 0x8, eQCmemo = 0x10, eQCaccount = 0x20, eQCreconciled = 0x40, eQCaction = 0x80, eQCshares = 0x100, eQCprice = 0x200, eQCperformance = 0x400, eQCloan = 0x800, eQCbalance = 0x1000, eQCcapitalgain = 0x2000, eQCend = 0x4000 };
enum EDetailLevel { eDetailNone = 0, eDetailAll, eDetailTop, eDetailGroup, eDetailTotal, eDetailEnd };
enum EChartType { eChartNone = 0, eChartLine, eChartBar, eChartPie, eChartRing, eChartStackedBar, eChartEnd };
......
......@@ -260,9 +260,10 @@ void ListTable::render(QString& result, QString& csv) const
i18nHeaders["periodicpayment"] = i18n("Periodic Payment");
i18nHeaders["finalpayment"] = i18n("Final Payment");
i18nHeaders["currentbalance"] = i18n("Current Balance");
i18nHeaders["capitalgain"] = i18n("Capital Gain");
// the list of columns which represent money, so we can display them correctly
QStringList moneyColumns = QString("value,shares,price,latestprice,netinvvalue,buys,sells,cashincome,reinvestincome,startingbal,fees,interest,payment,balance,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,loanamount,periodicpayment,finalpayment,currentbalance,startingbal,endingbal").split(',');
QStringList moneyColumns = QString("value,shares,price,latestprice,netinvvalue,buys,sells,cashincome,reinvestincome,startingbal,fees,interest,payment,balance,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,loanamount,periodicpayment,finalpayment,currentbalance,startingbal,endingbal,capitalgain").split(',');
// the list of columns which represent shares, which is like money except the
// transaction currency will not be displayed
......
......@@ -377,6 +377,10 @@ void QueryTable::init()
m_columns += ",startingbal,buys,sells,reinvestincome,cashincome,return,returninvestment";
m_subtotal = "endingbal";
}
if (qc & MyMoneyReport::eQCcapitalgain) {
m_columns += ",buys,sells";
m_subtotal = "capitalgain";
}
if (qc & MyMoneyReport::eQCloan) {
m_columns += ",payment,interest,fees";
m_postcolumns = "balance";
......@@ -1078,6 +1082,112 @@ void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow&
result["endingbal"] = endingBal.toString();
}
void QueryTable::constructCapitalGainRow(const ReportAccount& account, TableRow& result) const
{
MyMoneyFile* file = MyMoneyFile::instance();
MyMoneySecurity security;
MyMoneyMoney price;
MyMoneyMoney sellValue;
MyMoneyMoney buyValue;
MyMoneyMoney sellShares;
MyMoneyMoney buyShares;
//
// Calculate capital gain
//
// The following columns are created:
// Account, Buys, Sells, Capital Gain
MyMoneyReport report = m_config;
QDate startingDate;
QDate endingDate;
QDate newStartingDate;
QDate newEndingDate;
report.validDateRange(startingDate, endingDate);
newStartingDate = startingDate;
newEndingDate = endingDate;
MyMoneyMoney endingShares = file->balance(account.id(), endingDate); // get how many shares there are over zero value
bool reportedDateRange = true; // flag marking sell transactions between startingDate and endingDate
report.setReportAllSplits(false);
report.setConsiderCategory(true);
report.clearAccountFilter();
report.addAccount(account.id());
do {
QList<MyMoneyTransaction> transactions = file->transactionList(report);
for (QList<MyMoneyTransaction>::const_reverse_iterator it_t = transactions.crbegin(); it_t != transactions.crend(); ++it_t) {
MyMoneySplit shareSplit = (*it_t).splitByAccount(account.id());
MyMoneySplit assetAccountSplit;
QList<MyMoneySplit> feeSplits;
QList<MyMoneySplit> interestSplits;
MyMoneySecurity currency;
MyMoneySplit::investTransactionTypeE transactionType;
KMyMoneyUtils::dissectTransaction((*it_t), shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType);
//get price for the day of the transaction if we have to calculate base currency
//we are using the value of the split which is in deep currency
if (m_config.isConvertCurrency())
price = account.baseCurrencyPrice((*it_t).postDate()); //we only need base currency because the value is in deep currency
else
price = MyMoneyMoney::ONE;
MyMoneyMoney value = assetAccountSplit.value() * price;
MyMoneyMoney shares = shareSplit.shares();
if (transactionType == MyMoneySplit::BuyShares) {
if (endingShares.isZero()) { // add sold shares
if (buyShares + shares > sellShares.abs()) { // add partially sold shares
buyValue += (((sellShares.abs() - buyShares)) / shares) * value;
buyShares = sellShares.abs();
} else { // add wholly sold shares
buyValue += value;
buyShares += shares;
}
} else if (endingShares >= shares) { // substract not-sold shares
endingShares -= shares;
} else { // substract partially not-sold shares
buyValue += ((shares - endingShares) / shares) * value;
buyShares += (shares - endingShares);
endingShares = MyMoneyMoney(0);
}
} else if (transactionType == MyMoneySplit::SellShares && reportedDateRange) {
sellValue += value;
sellShares += shares;
} else if (transactionType == MyMoneySplit::SplitShares) { // shares variable is denominator of split ratio here
sellShares /= shares;
buyShares /= shares;
} else if (transactionType == MyMoneySplit::AddShares) { // added shares, when sold give 100% capital gain
if (endingShares.isZero()) { // add added shares
if (buyShares + shares > sellShares.abs()) { // add partially added shares
buyShares = sellShares.abs();
} else { // add wholly added shares
buyShares += shares;
}
} else if (endingShares >= shares) { // substract not-added shares
endingShares -= shares;
} else { // substract partially not-added shares
buyShares += (shares - endingShares);
endingShares = MyMoneyMoney(0);
}
} else if (transactionType == MyMoneySplit::RemoveShares && reportedDateRange) { // removed shares give no value in return so no capital gain on them
sellShares += shares;
}
}
reportedDateRange = false;
newEndingDate = newStartingDate;
newStartingDate = newStartingDate.addYears(-1);
report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier
} while (!sellShares.isZero() && account.openingDate() <= newEndingDate && sellShares.abs() > buyShares.abs());
result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType());
result["buys"] = buyValue.toString();
result["sells"] = sellValue.toString();
result["capitalgain"] = (buyValue + sellValue).toString();
report.setDateFilter(startingDate, endingDate); // reset data filter for next security
}
void QueryTable::constructAccountTable()
{
MyMoneyFile* file = MyMoneyFile::instance();
......@@ -1149,6 +1259,8 @@ void QueryTable::constructAccountTable()
if (m_config.queryColumns() == MyMoneyReport::eQCperformance) {
constructPerformanceRow(account, qaccountrow);
} else if (m_config.queryColumns() == MyMoneyReport::eQCcapitalgain) {
constructCapitalGainRow(account, qaccountrow);
} else
qaccountrow["equitytype"].clear();
......
......@@ -67,6 +67,7 @@ public:
protected:
void constructAccountTable();
void constructTransactionTable();
void constructCapitalGainRow(const ReportAccount& account, TableRow& result) const;
void constructPerformanceRow(const ReportAccount& account, TableRow& result) const;
void constructSplitsTable();
......
......@@ -1320,6 +1320,27 @@ void KReportsView::defaultReports(QList<ReportGroup>& groups)
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAccountByTopAccount,
MyMoneyReport::eQCcapitalgain,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Investment Capital Gains by Account"),
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eEquityType,
MyMoneyReport::eQCcapitalgain,
MyMoneyTransactionFilter::yearToDate,
MyMoneyReport::eDetailAll,
i18n("Investment Capital Gains by Type"),
i18n("Default Report")
));
list.back().setInvestmentsOnly(true);
list.push_back(MyMoneyReport(
MyMoneyReport::eAssetLiability,
MyMoneyReport::eMonths,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment