diff --git a/kmymoney/reports/listtable.cpp b/kmymoney/reports/listtable.cpp index 51a967d5e..ea10a2c7a 100644 --- a/kmymoney/reports/listtable.cpp +++ b/kmymoney/reports/listtable.cpp @@ -1,622 +1,745 @@ /*************************************************************************** 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; +QVector 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 ]) { + foreach (const auto criterion, m_sortCriteria) { + if (this->operator[](criterion) < _compare[criterion]) { result = true; break; - } else if (this->operator[](*it_criterion) > _compare[ *it_criterion ]) + } else if (this->operator[](criterion) > _compare[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"; + result.clear(); + csv.clear(); + result.append(QString::fromLatin1("

%1

\n").arg(m_config.name())); + csv.append(QString::fromLatin1("\"Report: %1\"\n").arg(m_config.name())); + //actual dates of the report - result += QString("
"); + result.append(QLatin1String("
")); 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"); + result.append(QLatin1String("
\n")); + result.append(QLatin1String("
 
\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"); + csv.append(QLatin1Char('\n')); } - - result += QString("
"); + result.append(QLatin1String("
")); 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()); + csv += i18n("All currencies converted to %1" , 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()); + csv += i18n("All values shown in %1 unless otherwise noted" , file->baseCurrency().name()); } - result += QString("
\n"); - result += QString("
 
\n"); + result.append(QLatin1String("
\n")); + result.append(QLatin1String("
 
\n")); + csv.append(QLatin1Char('\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(','); + QList columns = m_columns; + if (!m_subtotal.isEmpty() && m_subtotal.count() == 1) // constructPerformanceRow has subtotal columns already in columns + columns.append(m_subtotal); + QList postcolumns = m_postcolumns; if (!m_postcolumns.isEmpty()) // prevent creation of empty column - columns += postcolumns; + columns.append(postcolumns); + + result.append(QLatin1String("\n")); // // 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["lastprice"] = i18n("Last Price"); - i18nHeaders["buyprice"] = i18n("Buy Price"); - i18nHeaders["netinvvalue"] = i18n("Net Value"); - i18nHeaders["buys"] = i18n("Buy Value"); - i18nHeaders["sells"] = i18n("Sell Value"); - i18nHeaders["buysST"] = i18n("Short-term Buy Value"); - i18nHeaders["sellsST"] = i18n("Short-term Sell Value"); - i18nHeaders["buysLT"] = i18n("Long-term Buy Value"); - i18nHeaders["sellsLT"] = i18n("Long-term Sell Value"); - i18nHeaders["reinvestincome"] = i18n("Dividends Reinvested"); - i18nHeaders["cashincome"] = i18n("Dividends Paid Out"); - i18nHeaders["startingbal"] = i18n("Starting Balance"); - i18nHeaders["endingbal"] = i18n("Ending Balance"); - i18nHeaders["marketvalue"] = i18n("Market Value"); - 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["percentagegain"] = i18n("Percentage 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,price,lastprice,buyprice,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,marketvalue").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,percentagegain").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"; - - QStringList::const_iterator it_column = columns.constBegin(); - while (it_column != columns.constEnd()) { - QString i18nName = i18nHeaders[*it_column]; - if (i18nName.isEmpty()) - i18nName = *it_column; - result += ""; - csv += i18nName + ','; - ++it_column; + foreach (const auto cellType, columns) { + result.append(QString::fromLatin1("").arg(tableHeader(cellType))); + csv.append(tableHeader(cellType) + QLatin1Char(',')); } + csv.chop(1); // remove last ',' character - result += "\n"; - csv = csv.left(csv.length() - 1); - csv += '\n'; + result.append(QLatin1String("\n")); + csv.append(QLatin1Char('\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) { + for (int i = 0; i < m_group.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(); + for (QList::ConstIterator it_row = m_rows.constBegin(); + it_row != m_rows.constEnd(); ++it_row) { - - /** rank can be: + /* 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"]; + const int rowRank = (*it_row).value(ctRank).toInt(); // 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)); + for (int i = 0; i < m_group.count(); ++i) { + QString curGrpName = (*it_row).value(m_group.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; + int lowestGroup = m_group.count() - 1; if (!m_config.isHideTransactions() || i != lowestGroup) { row_odd = true; - result += "" - "\n"; - csv += "\"" + curGrpName + "\"\n"; + result.append(QString::fromLatin1("" + "\n").arg(QString::number(i), + QString::number(columns.count()), + curGrpName)); + csv.append(QString::fromLatin1("\"%1\"\n")).arg(curGrpName); } 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") { + 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")) + if (!columns.contains(ctBalance)) continue; - result += QString("").arg((* it_row)["id"]); - // ***DV*** - } else if (rowRank == "1") { + result.append(QString::fromLatin1("").arg((*it_row).value(ctID))); + // ***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") { + tlink = QString::fromLatin1("id=%1&tid=%2").arg((*it_row).value(ctAccountID), (*it_row).value(ctID)); + result.append(QString::fromLatin1("").arg(row_odd ? QLatin1String("odd") : QLatin1String("even"))); + } else if (rowRank == 2) { + result.append(QString::fromLatin1("").arg(row_odd ? QLatin1Char('1') : QLatin1Char('0'))); + } 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 + 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") { + } else if (rowRank == 4) { if (nextRow != m_rows.end()) { if (isLowestGroupTotal && m_config.isHideTransactions()) { - result += QString(""); + result.append(QLatin1String("")); isLowestGroupTotal = false; - } else if ((*nextRow)["rank"] == "5") - result += QString(""); - else - result += QString(""); - } else - result += QString(""); - } else if (rowRank == "5") { + } else if ((*nextRow).value(ctRank) == QLatin1String("5")) { + result.append(QLatin1String("")); + } else { + result.append(QLatin1String("")); + } + } else { + result.append(QLatin1String("")); + } + } else if (rowRank == 5) { if (nextRow != m_rows.end()) { - if ((*nextRow)["rank"] == "5") - result += QString(""); + if ((*nextRow).value(ctRank) == QLatin1String("5")) + result.append(QLatin1String("")); else - result += QString(""); + result.append(QLatin1String("")); } - } else - result += QString(""); - } else - result += QString("").arg(row_odd ? "row-odd " : "row-even"); - } else + } else { + result.append(QLatin1String("")); + } + } else { + result.append(QString::fromLatin1("").arg(row_odd ? QLatin1String("odd") : QLatin1String("even"))); + } + } else { continue; + } // // Columns // - QStringList::const_iterator it_column = columns.constBegin(); + QList::ConstIterator it_column = columns.constBegin(); while (it_column != columns.constEnd()) { - QString data = (*it_row)[*it_column]; + QString data = (*it_row).value(*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 = ""; + if (rowRank == 2) { + if (*it_column == ctValue) + data = (*it_row).value(ctSplit); + else if (*it_column == ctPostDate + || *it_column == ctNumber + || *it_column == ctPayee + || *it_column == ctTag + || *it_column == ctAction + || *it_column == ctShares + || *it_column == ctPrice + || *it_column == ctNextDueDate + || *it_column == ctBalance + || *it_column == ctAccount + || *it_column == ctName) + data.clear(); } // ***DV*** - else if (rowRank == "0" || rowRank == "3") { - if (*it_column == "balance") { - data = (* it_row)["balance"]; - if ((* it_row)["id"] == "A") { // opening balance? + else if (rowRank == 0 || rowRank == 3) { + if (*it_column == ctBalance) { + data = (*it_row).value(ctBalance); + if ((*it_row).value(ctID) == QLatin1String("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"); + if ((*it_column == ctPayee) || + (*it_column == ctCategory) || + (*it_column == ctMemo)) { + if (!(*it_row).value(ctShares).isEmpty()) { + data = ((*it_row).value(ctID) == QLatin1String("A")) + ? i18n("Initial Market Value") + : i18n("Ending Market Value"); } else { - data = ((* it_row)["id"] == "A") - ? i18n("Opening Balance") - : i18n("Closing Balance"); + data = ((*it_row).value(ctID) == QLatin1String("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") { + else if (*it_column == ctBalance && rowRank == 1) { // Take the balance off the deepest group iterator - balanceChange += MyMoneyMoney((*it_row).value("value", "0")); + balanceChange += MyMoneyMoney((*it_row).value(ctValue, QLatin1String("0"))); data = (balanceChange + startingBalance).toString(); - } else if ((rowRank == "4" || rowRank == "5")) { + } 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 += "")); + result.append(QLatin1String("")); ++it_column; continue; } else if (!m_subtotal.contains(*it_column)) { // don't display e.g. account in totals row - result.append(QLatin1Literal("")); + result.append(QLatin1String("")); ++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(""); + tlinkBegin = QString::fromLatin1("").arg(tlink); + tlinkEnd = QLatin1String(""); } - QString currencyID = (*it_row)["currency"]; + QString currencyID = (*it_row).value(ctCurrency); 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("").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).endsWith(QLatin1String("price"))) { - int pricePrecision = file->security(file->account((*it_row)["accountid"]).currencyId()).pricePrecision(); - result += QString("") - .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("").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); + switch (cellGroup(*it_column)) { + case cgMoney: + if (data.isEmpty()) { + result.append(QString::fromLatin1("") + .arg((*it_column == ctValue) ? QLatin1String(" class=\"value\"") : QString())); + csv.append(QLatin1String("\"\",")); + } else if (MyMoneyMoney(data) == MyMoneyMoney::autoCalc) { + result.append(QString::fromLatin1("%3%2%4") + .arg((*it_column == ctValue) ? QLatin1String(" class=\"value\"") : QString(), + i18n("Calculated"), tlinkBegin, tlinkEnd)); + csv.append(QString::fromLatin1("\"%1\",").arg(i18n("Calculated"))); + } else { + result.append(QString::fromLatin1("%4%2 %3%5") + .arg((*it_column == ctValue) ? QLatin1String(" class=\"value\"") : QString(), + currencyID, + MyMoneyMoney(data).formatMoney(fraction), + tlinkBegin, tlinkEnd)); + csv.append(QString::fromLatin1("\"%1 %2\",").arg(currencyID, + MyMoneyMoney(data).formatMoney(fraction, false))); + } + break; + case cgPercent: + if (data.isEmpty()) { + result.append(QLatin1String("")); + csv.append(QLatin1String("\"\",")); + } else { + data = (MyMoneyMoney(data) * MyMoneyMoney(100, 1)).formatMoney(fraction); + result.append(QString::fromLatin1("").arg(data, tlinkBegin, tlinkEnd)); + csv.append(QString::fromLatin1("%1%,").arg(data)); + } + break; + case cgPrice: + { + int pricePrecision = file->security(file->account((*it_row).value(ctAccountID)).currencyId()).pricePrecision(); + result.append(QString::fromLatin1("") + .arg(MyMoneyMoney(data).formatMoney(QString(), pricePrecision), + currencyID, tlinkBegin, tlinkEnd)); + csv.append(QString::fromLatin1("\"%1 %2\",").arg(currencyID, + MyMoneyMoney(data).formatMoney(QString(), pricePrecision, false))); } - result += QString("").arg(data, tlinkBegin, tlinkEnd); - } else { - result += QString("").arg(data, tlinkBegin, tlinkEnd); - csv += "\"" + data + "\","; + break; + case cgShares: + if (data.isEmpty()) { + result.append(QLatin1String("")); + csv.append(QLatin1String("\"\",")); + } else { + int sharesPrecision = MyMoneyMoney::denomToPrec(file->security(file->account((*it_row).value(ctAccountID)).currencyId()).smallestAccountFraction()); + result += QString::fromLatin1("").arg(MyMoneyMoney(data).formatMoney(QString(), sharesPrecision), + tlinkBegin, tlinkEnd); + csv.append(QString::fromLatin1("\"%1\",").arg(MyMoneyMoney(data).formatMoney(QString(), sharesPrecision, false))); + } + break; + case cgDate: + // do this before we possibly change data + csv.append(QString::fromLatin1("\"%1\",").arg(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.append(QString::fromLatin1("").arg(data, tlinkBegin, tlinkEnd, QString::number(prevGrpNames.count() - 1))); + break; + default: + result.append(QString::fromLatin1("").arg(data, tlinkBegin, tlinkEnd, QString::number(prevGrpNames.count() - 1))); + csv.append(QString::fromLatin1("\"%1\",").arg(data)); + break; } ++it_column; tlink.clear(); } - result += "\n"; - csv = csv.left(csv.length() - 1); // remove final comma - csv += '\n'; + result.append(QLatin1String("\n")); + csv.chop(1); // remove final comma + csv.append(QLatin1Char('\n')); } - result += "
" + i18nName + "%1
" + - curGrpName + "
%3
"; - if (rowRank == "4") { - if (!(*it_row)["depth"].isEmpty()) - result += i18nc("Total balance", "Total") + ' ' + prevGrpNames.at((*it_row)["depth"].toInt()); + result.append(QString::fromLatin1("").arg((*it_row).value(ctDepth))); + if (rowRank == 4) { + if (!(*it_row).value(ctDepth).isEmpty()) + result += i18nc("Total balance", "Total") + QLatin1Char(' ') + prevGrpNames.at((*it_row).value(ctDepth).toInt()); else result += i18n("Grand Total"); } - result.append(QLatin1Literal("%2%1%3%3%2 %1%4%2%1%%3%2%1%%3%3%2 %1%4%2%1%3%2%1%3%2%1%3%2%1%3%2%1%3
\n"; + result.append(QLatin1String("\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); } } +ListTable::cellGroupE ListTable::cellGroup(const cellTypeE cellType) +{ + switch (cellType) { + // the list of columns which represent money, so we can display them correctly + case ctValue: + case ctNetInvValue: + case ctMarketValue: + case ctBuys: + case ctSells: + case ctBuysST: + case ctSellsST: + case ctBuysLT: + case ctSellsLT: + case ctCapitalGain: + case ctCapitalGainST: + case ctCapitalGainLT: + case ctCashIncome: + case ctReinvestIncome: + case ctFees: + case ctInterest: + case ctStartingBalance: + case ctEndingBalance: + case ctBalance: + case ctCurrentBalance: + case ctBalanceWarning: + case ctMaxBalanceLimit: + case ctCreditWarning: + case ctMaxCreditLimit: + case ctLoanAmount: + case ctPeriodicPayment: + case ctFinalPayment: + case ctPayment: + return cgMoney; + case ctPrice: + case ctLastPrice: + case ctBuyPrice: + return cgPrice; + /* the list of columns which represent shares, which is like money except the + transaction currency will not be displayed*/ + case ctShares: + return cgShares; + // the list of columns which represent a percentage, so we can display them correctly + case ctReturn: + case ctReturnInvestment: + case ctInterestRate: + case ctPercentageGain: + return cgPercent; + // the list of columns which represent dates, so we can display them correctly + case ctPostDate: + case ctEntryDate: + case ctNextDueDate: + case ctOpeningDate: + case ctNextInterestChange: + return cgDate; + default: + break; + } + return cgMisc; +} + +QString ListTable::tableHeader(const cellTypeE cellType) +{ + switch (cellType) { + case ctPostDate: + return i18n("Date"); + case ctValue: + return i18n("Amount"); + case ctNumber: + return i18n("Num"); + case ctPayee: + return i18n("Payee"); + case ctTag: + return i18n("Tags"); + case ctCategory: + return i18n("Category"); + case ctAccount: + return i18n("Account"); + case ctMemo: + return i18n("Memo"); + case ctTopCategory: + return i18n("Top Category"); + case ctCategoryType: + return i18n("Category Type"); + case ctMonth: + return i18n("Month"); + case ctWeek: + return i18n("Week"); + case ctReconcileFlag: + return i18n("Reconciled"); + case ctAction: + return i18n("Action"); + case ctShares: + return i18n("Shares"); + case ctPrice: + return i18n("Price"); + case ctLastPrice: + return i18n("Last Price"); + case ctBuyPrice: + return i18n("Buy Price"); + case ctNetInvValue: + return i18n("Net Value"); + case ctBuys: + return i18n("Buy Value"); + case ctSells: + return i18n("Sell Value"); + case ctBuysST: + return i18n("Short-term Buy Value"); + case ctSellsST: + return i18n("Short-term Sell Value"); + case ctBuysLT: + return i18n("Long-term Buy Value"); + case ctSellsLT: + return i18n("Long-term Sell Value"); + case ctReinvestIncome: + return i18n("Dividends Reinvested"); + case ctCashIncome: + return i18n("Dividends Paid Out"); + case ctStartingBalance: + return i18n("Starting Balance"); + case ctEndingBalance: + return i18n("Ending Balance"); + case ctMarketValue: + return i18n("Market Value"); + case ctReturn: + return i18n("Annualized Return"); + case ctReturnInvestment: + return i18n("Return On Investment"); + case ctFees: + return i18n("Fees"); + case ctInterest: + return i18n("Interest"); + case ctPayment: + return i18n("Payment"); + case ctBalance: + return i18n("Balance"); + case ctType: + return i18n("Type"); + case ctName: + return i18nc("Account name", "Name"); + case ctNextDueDate: + return i18n("Next Due Date"); + case ctOccurence: + return i18n("Occurrence"); // krazy:exclude=spelling + case ctPaymentType: + return i18n("Payment Method"); + case ctInstitution: + return i18n("Institution"); + case ctDescription: + return i18n("Description"); + case ctOpeningDate: + return i18n("Opening Date"); + case ctCurrencyName: + return i18n("Currency"); + case ctBalanceWarning: + return i18n("Balance Early Warning"); + case ctMaxBalanceLimit: + return i18n("Balance Max Limit"); + case ctCreditWarning: + return i18n("Credit Early Warning"); + case ctMaxCreditLimit: + return i18n("Credit Max Limit"); + case ctTax: + return i18n("Tax"); + case ctFavorite: + return i18n("Preferred"); + case ctLoanAmount: + return i18n("Loan Amount"); + case ctInterestRate: + return i18n("Interest Rate"); + case ctNextInterestChange: + return i18n("Next Interest Change"); + case ctPeriodicPayment: + return i18n("Periodic Payment"); + case ctFinalPayment: + return i18n("Final Payment"); + case ctCurrentBalance: + return i18n("Current Balance"); + case ctCapitalGain: + return i18n("Capital Gain"); + case ctPercentageGain: + return i18n("Percentage Gain"); + case ctCapitalGainST: + return i18n("Short-term Gain"); + case ctCapitalGainLT: + return i18n("Long-term Gain"); + default: + break; + } + return QLatin1String("None"); +} } diff --git a/kmymoney/reports/listtable.h b/kmymoney/reports/listtable.h index 6293b31b6..d2c1e301b 100644 --- a/kmymoney/reports/listtable.h +++ b/kmymoney/reports/listtable.h @@ -1,127 +1,154 @@ /*************************************************************************** listtable.h ------------------- begin : Sat 28 jun 2008 copyright : (C) 2004-2005 by Ace Jones 2008 by Alvaro Soliverez ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef LISTTABLE_H #define LISTTABLE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyreport.h" #include "reporttable.h" namespace reports { class ReportAccount; /** * Calculates a query of information about the transaction database. * * This is a middle-layer class, between the implementing classes and the engine. The * MyMoneyReport class holds only the CONFIGURATION parameters. This * class has some common methods used by querytable and objectinfo classes * * @author Alvaro Soliverez * * @short **/ class ListTable : public ReportTable { public: ListTable(const MyMoneyReport&); QString renderBody() const; QString renderCSV() const; void drawChart(KReportChartView&) const {} void dump(const QString& file, const QString& context = QString()) const; void init(); public: + enum cellTypeE /*{*/ /*Money*/ + {ctValue, ctNetInvValue, ctMarketValue, + ctPrice, ctLastPrice, ctBuyPrice, + ctBuys, ctSells, ctBuysST, ctSellsST, ctBuysLT, ctSellsLT, + ctCapitalGain, ctCapitalGainST,ctCapitalGainLT, + ctCashIncome, ctReinvestIncome, + ctFees, ctInterest, + ctStartingBalance, ctEndingBalance, ctBalance, ctCurrentBalance, + ctBalanceWarning, ctMaxBalanceLimit, ctOpeningBalance, + ctCreditWarning, ctMaxCreditLimit, + ctLoanAmount, ctPeriodicPayment, ctFinalPayment, ctPayment, + /*Shares*/ + ctShares, + /*Percent*/ + ctReturn, ctReturnInvestment, ctInterestRate, ctPercentageGain, + /*Date*/ + ctPostDate, ctEntryDate, ctNextDueDate, ctOpeningDate, ctNextInterestChange, + ctMonth, ctWeek, ctReconcileDate, + /*Misc*/ + ctCurrency, ctCurrencyName, ctCommodity, ctID, + ctRank, ctSplit, ctMemo, + ctAccount, ctAccountID, ctTopAccount, ctInvestAccount, ctInstitution, + ctCategory, ctTopCategory, ctCategoryType, + ctNumber, ctReconcileFlag, + ctAction, ctTag, ctPayee, ctEquityType, ctType, ctName, + ctDepth, ctRowsCount, ctTax, ctFavorite, ctDescription, ctOccurence, ctPaymentType + }; /** * Contains a single row in the table. * * Each column is a key/value pair, both strings. This class is just * a QMap with the added ability to specify which columns you'd like to * use as a sort key when you qHeapSort a list of these TableRows */ - class TableRow: public QMap + class TableRow: public QMap { public: bool operator< (const TableRow&) const; bool operator<= (const TableRow&) const; bool operator> (const TableRow&) const; bool operator== (const TableRow&) const; - static void setSortCriteria(const QString& _criteria) { - m_sortCriteria = _criteria.split(","); + static void setSortCriteria(const QVector& _criteria) { + m_sortCriteria = _criteria; } private: - static QStringList m_sortCriteria; + static QVector m_sortCriteria; }; const QList& rows() { return m_rows; - }; + } protected: void render(QString&, QString&) const; /** * If not in expert mode, include all subaccounts for each selected * investment account. * For investment-only reports, it will also exclude the subaccounts * that have a zero balance */ void includeInvestmentSubAccounts(); QList m_rows; - QString m_group; + QList m_group; /** * Comma-separated list of columns to place BEFORE the subtotal column */ - QString m_columns; + QList m_columns; /** * Name of the subtotal column */ - QString m_subtotal; + QList m_subtotal; /** * Comma-separated list of columns to place AFTER the subtotal column */ - QString m_postcolumns; - QString m_summarize; - QString m_propagate; + QList m_postcolumns; MyMoneyReport m_config; - - +private: + enum cellGroupE { cgMoney, cgShares, cgPercent, cgDate, cgPrice, cgMisc }; + static cellGroupE cellGroup(const cellTypeE cellType); + static QString tableHeader(const cellTypeE cellType); }; } #endif diff --git a/kmymoney/reports/objectinfotable.cpp b/kmymoney/reports/objectinfotable.cpp index 2b11c91dc..5b59848b5 100644 --- a/kmymoney/reports/objectinfotable.cpp +++ b/kmymoney/reports/objectinfotable.cpp @@ -1,354 +1,365 @@ /*************************************************************************** objectinfotable.cpp ------------------- begin : Sat 28 jun 2008 copyright : (C) 2004-2005 by Ace Jones 2008 by Alvaro Soliverez ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "objectinfotable.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneyreport.h" #include "mymoneyexception.h" #include "kmymoneyutils.h" #include "reportaccount.h" #include "reportdebug.h" namespace reports { // **************************************************************************** // // ObjectInfoTable 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 * */ ObjectInfoTable::ObjectInfoTable(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 ObjectInfoTable::init() { + m_columns.clear(); + m_group.clear(); + m_subtotal.clear(); switch (m_config.rowType()) { case MyMoneyReport::eSchedule: constructScheduleTable(); - m_columns = "nextduedate,name"; + m_columns << ctNextDueDate << ctName; break; case MyMoneyReport::eAccountInfo: constructAccountTable(); - m_columns = "institution,type,name"; + m_columns << ctInstitution << ctType << ctName; break; case MyMoneyReport::eAccountLoanInfo: constructAccountLoanTable(); - m_columns = "institution,type,name"; + m_columns << ctInstitution << ctType << ctName; break; default: break; } // Sort the data to match the report definition - m_subtotal = "value"; + m_subtotal << ctValue; switch (m_config.rowType()) { case MyMoneyReport::eSchedule: - m_group = "type"; - m_subtotal = "value"; + m_group << ctType; + m_subtotal << ctValue; break; case MyMoneyReport::eAccountInfo: case MyMoneyReport::eAccountLoanInfo: - m_group = "topcategory,institution"; - m_subtotal = "currentbalance"; + m_group << ctTopCategory << ctInstitution; + m_subtotal << ctCurrentBalance; break; default: throw MYMONEYEXCEPTION("ObjectInfoTable::ObjectInfoTable(): unhandled row type"); } - QString sort = m_group + ',' + m_columns + ",id,rank"; + QVector sort = QVector::fromList(m_group) << QVector::fromList(m_columns) << ctID << ctRank; switch (m_config.rowType()) { case MyMoneyReport::eSchedule: if (m_config.detailLevel() == MyMoneyReport::eDetailAll) { - m_columns = "name,payee,paymenttype,occurence,nextduedate,category"; // krazy:exclude=spelling + m_columns << ctName << ctPayee << ctPaymentType << ctOccurence + << ctNextDueDate << ctCategory; // krazy:exclude=spelling } else { - m_columns = "name,payee,paymenttype,occurence,nextduedate"; // krazy:exclude=spelling + m_columns << ctName << ctPayee << ctPaymentType << ctOccurence + << ctNextDueDate; // krazy:exclude=spelling } break; case MyMoneyReport::eAccountInfo: - m_columns = "type,name,number,description,openingdate,currencyname,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,tax,favorite"; + m_columns << ctType << ctName << ctNumber << ctDescription + << ctOpeningDate << ctCurrencyName << ctBalanceWarning + << ctCreditWarning << ctMaxCreditLimit + << ctTax << ctFavorite; break; case MyMoneyReport::eAccountLoanInfo: - m_columns = "type,name,number,description,openingdate,currencyname,payee,loanamount,interestrate,nextinterestchange,periodicpayment,finalpayment,favorite"; + m_columns << ctType << ctName << ctNumber << ctDescription + << ctOpeningDate << ctCurrencyName << ctPayee + << ctLoanAmount << ctInterestRate << ctNextInterestChange + << ctPeriodicPayment << ctFinalPayment << ctFavorite; break; default: - m_columns = ""; + m_columns.clear(); } TableRow::setSortCriteria(sort); qSort(m_rows); } void ObjectInfoTable::constructScheduleTable() { MyMoneyFile* file = MyMoneyFile::instance(); QList schedules; schedules = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, m_config.fromDate(), m_config.toDate()); QList::const_iterator it_schedule = schedules.constBegin(); while (it_schedule != schedules.constEnd()) { MyMoneySchedule schedule = *it_schedule; ReportAccount account = schedule.account(); if (m_config.includes(account)) { //get fraction for account int fraction = account.fraction(); //use base currency fraction if not initialized if (fraction == -1) fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction(); TableRow scheduleRow; //convert to base currency if needed MyMoneyMoney xr = MyMoneyMoney::ONE; if (m_config.isConvertCurrency() && account.isForeignCurrency()) { xr = account.baseCurrencyPrice(QDate::currentDate()).reduce(); } // help for sort and render functions - scheduleRow["rank"] = '0'; + scheduleRow[ctRank] = QLatin1Char('0'); //schedule data - scheduleRow["id"] = schedule.id(); - scheduleRow["name"] = schedule.name(); - scheduleRow["nextduedate"] = schedule.nextDueDate().toString(Qt::ISODate); - scheduleRow["type"] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); - scheduleRow["occurence"] = i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1()); // krazy:exclude=spelling - scheduleRow["paymenttype"] = KMyMoneyUtils::paymentMethodToString(schedule.paymentType()); + scheduleRow[ctID] = schedule.id(); + scheduleRow[ctName] = schedule.name(); + scheduleRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate); + scheduleRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); + scheduleRow[ctOccurence] = i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1()); // krazy:exclude=spelling + scheduleRow[ctPaymentType] = KMyMoneyUtils::paymentMethodToString(schedule.paymentType()); //scheduleRow["category"] = account.name(); //to get the payee we must look into the splits of the transaction MyMoneyTransaction transaction = schedule.transaction(); MyMoneySplit split = transaction.splitByAccount(account.id(), true); - scheduleRow["value"] = (split.value() * xr).toString(); + scheduleRow[ctValue] = (split.value() * xr).toString(); MyMoneyPayee payee = file->payee(split.payeeId()); - scheduleRow["payee"] = payee.name(); + scheduleRow[ctPayee] = payee.name(); m_rows += scheduleRow; //the text matches the main split bool transaction_text = m_config.match(&split); if (m_config.detailLevel() == MyMoneyReport::eDetailAll) { //get the information for all splits QList splits = transaction.splits(); QList::const_iterator split_it = splits.constBegin(); for (; split_it != splits.constEnd(); ++split_it) { TableRow splitRow; ReportAccount splitAcc = (*split_it).accountId(); - splitRow["rank"] = '1'; - splitRow["id"] = schedule.id(); - splitRow["name"] = schedule.name(); - splitRow["type"] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); - splitRow["nextduedate"] = schedule.nextDueDate().toString(Qt::ISODate); + splitRow[ctRank] = QLatin1Char('1'); + splitRow[ctID] = schedule.id(); + splitRow[ctName] = schedule.name(); + splitRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); + splitRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate); if ((*split_it).value() == MyMoneyMoney::autoCalc) { - splitRow["split"] = MyMoneyMoney::autoCalc.toString(); + splitRow[ctSplit] = MyMoneyMoney::autoCalc.toString(); } else if (! splitAcc.isIncomeExpense()) { - splitRow["split"] = (*split_it).value().toString(); + splitRow[ctSplit] = (*split_it).value().toString(); } else { - splitRow["split"] = (- (*split_it).value()).toString(); + splitRow[ctSplit] = (- (*split_it).value()).toString(); } //if it is an assett account, mark it as a transfer if (! splitAcc.isIncomeExpense()) { - splitRow["category"] = ((* split_it).value().isNegative()) + splitRow[ctCategory] = ((* split_it).value().isNegative()) ? i18n("Transfer from %1" , splitAcc.fullName()) : i18n("Transfer to %1" , splitAcc.fullName()); } else { - splitRow ["category"] = splitAcc.fullName(); + splitRow [ctCategory] = splitAcc.fullName(); } //add the split only if it matches the text or it matches the main split if (m_config.match(&(*split_it)) || transaction_text) m_rows += splitRow; } } } ++it_schedule; } } void ObjectInfoTable::constructAccountTable() { MyMoneyFile* file = MyMoneyFile::instance(); //make sure we have all subaccounts of investment accounts includeInvestmentSubAccounts(); QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { TableRow accountRow; ReportAccount account = *it_account; if (m_config.includes(account) && account.accountType() != MyMoneyAccount::Stock && !account.isClosed()) { MyMoneyMoney value; - accountRow["rank"] = '0'; - accountRow["topcategory"] = KMyMoneyUtils::accountTypeToString(account.accountGroup()); - accountRow["institution"] = (file->institution(account.institutionId())).name(); - accountRow["type"] = KMyMoneyUtils::accountTypeToString(account.accountType()); - accountRow["name"] = account.name(); - accountRow["number"] = account.number(); - accountRow["description"] = account.description(); - accountRow["openingdate"] = account.openingDate().toString(Qt::ISODate); + accountRow[ctRank] = QLatin1Char('0'); + accountRow[ctTopCategory] = KMyMoneyUtils::accountTypeToString(account.accountGroup()); + accountRow[ctInstitution] = (file->institution(account.institutionId())).name(); + accountRow[ctType] = KMyMoneyUtils::accountTypeToString(account.accountType()); + accountRow[ctName] = account.name(); + accountRow[ctNumber] = account.number(); + accountRow[ctDescription] = account.description(); + accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate); //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol(); - accountRow["currencyname"] = (file->currency(account.currencyId())).name(); - accountRow["balancewarning"] = account.value("minBalanceEarly"); - accountRow["maxbalancelimit"] = account.value("minBalanceAbsolute"); - accountRow["creditwarning"] = account.value("maxCreditEarly"); - accountRow["maxcreditlimit"] = account.value("maxCreditAbsolute"); - accountRow["tax"] = account.value("Tax") == QLatin1String("Yes") ? i18nc("Is this a tax account?", "Yes") : QString(); - accountRow["openingbalance"] = account.value("OpeningBalanceAccount") == QLatin1String("Yes") ? i18nc("Is this an opening balance account?", "Yes") : QString(); - accountRow["favorite"] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString(); + accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name(); + accountRow[ctBalanceWarning] = account.value("minBalanceEarly"); + accountRow[ctMaxBalanceLimit] = account.value("minBalanceAbsolute"); + accountRow[ctCreditWarning] = account.value("maxCreditEarly"); + accountRow[ctMaxCreditLimit] = account.value("maxCreditAbsolute"); + accountRow[ctTax] = account.value("Tax") == QLatin1String("Yes") ? i18nc("Is this a tax account?", "Yes") : QString(); + accountRow[ctOpeningBalance] = account.value("OpeningBalanceAccount") == QLatin1String("Yes") ? i18nc("Is this an opening balance account?", "Yes") : QString(); + accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString(); //investment accounts show the balances of all its subaccounts if (account.accountType() == MyMoneyAccount::Investment) { value = investmentBalance(account); } else { value = file->balance(account.id()); } //convert to base currency if needed if (m_config.isConvertCurrency() && account.isForeignCurrency()) { MyMoneyMoney xr = account.baseCurrencyPrice(QDate::currentDate()).reduce(); value = value * xr; } - accountRow["currentbalance"] = value.toString(); + accountRow[ctCurrentBalance] = value.toString(); m_rows += accountRow; } ++it_account; } } void ObjectInfoTable::constructAccountLoanTable() { MyMoneyFile* file = MyMoneyFile::instance(); QList accounts; file->accountList(accounts); QList::const_iterator it_account = accounts.constBegin(); while (it_account != accounts.constEnd()) { TableRow accountRow; ReportAccount account = *it_account; MyMoneyAccountLoan loan = *it_account; if (m_config.includes(account) && account.isLoan() && !account.isClosed()) { //convert to base currency if needed MyMoneyMoney xr = MyMoneyMoney::ONE; if (m_config.isConvertCurrency() && account.isForeignCurrency()) { xr = account.baseCurrencyPrice(QDate::currentDate()).reduce(); } - accountRow["rank"] = '0'; - accountRow["topcategory"] = KMyMoneyUtils::accountTypeToString(account.accountGroup()); - accountRow["institution"] = (file->institution(account.institutionId())).name(); - accountRow["type"] = KMyMoneyUtils::accountTypeToString(account.accountType()); - accountRow["name"] = account.name(); - accountRow["number"] = account.number(); - accountRow["description"] = account.description(); - accountRow["openingdate"] = account.openingDate().toString(Qt::ISODate); + accountRow[ctRank] = QLatin1Char('0'); + accountRow[ctTopCategory] = KMyMoneyUtils::accountTypeToString(account.accountGroup()); + accountRow[ctInstitution] = (file->institution(account.institutionId())).name(); + accountRow[ctType] = KMyMoneyUtils::accountTypeToString(account.accountType()); + accountRow[ctName] = account.name(); + accountRow[ctNumber] = account.number(); + accountRow[ctDescription] = account.description(); + accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate); //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol(); - accountRow["currencyname"] = (file->currency(account.currencyId())).name(); - accountRow["payee"] = file->payee(loan.payee()).name(); - accountRow["loanamount"] = (loan.loanAmount() * xr).toString(); - accountRow["interestrate"] = (loan.interestRate(QDate::currentDate()) / MyMoneyMoney(100, 1) * xr).toString(); - accountRow["nextinterestchange"] = loan.nextInterestChange().toString(Qt::ISODate); - accountRow["periodicpayment"] = (loan.periodicPayment() * xr).toString(); - accountRow["finalpayment"] = (loan.finalPayment() * xr).toString(); - accountRow["favorite"] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString(); + accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name(); + accountRow[ctPayee] = file->payee(loan.payee()).name(); + accountRow[ctLoanAmount] = (loan.loanAmount() * xr).toString(); + accountRow[ctInterestRate] = (loan.interestRate(QDate::currentDate()) / MyMoneyMoney(100, 1) * xr).toString(); + accountRow[ctNextInterestChange] = loan.nextInterestChange().toString(Qt::ISODate); + accountRow[ctPeriodicPayment] = (loan.periodicPayment() * xr).toString(); + accountRow[ctFinalPayment] = (loan.finalPayment() * xr).toString(); + accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString(); MyMoneyMoney value = file->balance(account.id()); value = value * xr; - accountRow["currentbalance"] = value.toString(); + accountRow[ctCurrentBalance] = value.toString(); m_rows += accountRow; } ++it_account; } } MyMoneyMoney ObjectInfoTable::investmentBalance(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyMoney value = file->balance(acc.id()); QStringList accList = acc.accountList(); QStringList::const_iterator it_a = accList.constBegin(); for (; it_a != acc.accountList().constEnd(); ++it_a) { MyMoneyAccount stock = file->account(*it_a); try { MyMoneyMoney val; MyMoneyMoney balance = file->balance(stock.id()); MyMoneySecurity security = file->security(stock.currencyId()); const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency()); val = balance * price.rate(security.tradingCurrency()); // adjust value of security to the currency of the account MyMoneySecurity accountCurrency = file->currency(acc.currencyId()); val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id()); val = val.convert(acc.fraction()); value += val; } catch (const MyMoneyException &e) { qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what()))); } } return value; } } diff --git a/kmymoney/reports/querytable.cpp b/kmymoney/reports/querytable.cpp index 47e7c4ca3..58c306fb2 100644 --- a/kmymoney/reports/querytable.cpp +++ b/kmymoney/reports/querytable.cpp @@ -1,2114 +1,2136 @@ /*************************************************************************** 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() { + m_columns.clear(); + m_group.clear(); + m_subtotal.clear(); + m_postcolumns.clear(); switch (m_config.rowType()) { case MyMoneyReport::eAccountByTopAccount: case MyMoneyReport::eEquityType: case MyMoneyReport::eAccountType: case MyMoneyReport::eInstitution: constructAccountTable(); - m_columns = "account"; + m_columns << ctAccount; break; case MyMoneyReport::eAccount: constructTransactionTable(); - m_columns = "accountid,postdate"; + m_columns << ctAccountID << ctPostDate; break; case MyMoneyReport::ePayee: case MyMoneyReport::eTag: case MyMoneyReport::eMonth: case MyMoneyReport::eWeek: constructTransactionTable(); - m_columns = "postdate,account"; + m_columns << ctPostDate << ctAccount; break; case MyMoneyReport::eCashFlow: constructSplitsTable(); - m_columns = "postdate"; + m_columns << ctPostDate; break; default: constructTransactionTable(); - m_columns = "postdate"; + m_columns << ctPostDate; } // Sort the data to match the report definition - m_subtotal = "value"; + m_subtotal << ctValue; switch (m_config.rowType()) { case MyMoneyReport::eCashFlow: - m_group = "categorytype,topcategory,category"; + m_group << ctCategoryType << ctTopCategory << ctCategory; break; case MyMoneyReport::eCategory: - m_group = "categorytype,topcategory,category"; + m_group << ctCategoryType << ctTopCategory << ctCategory; break; case MyMoneyReport::eTopCategory: - m_group = "categorytype,topcategory"; + m_group << ctCategoryType << ctTopCategory; break; case MyMoneyReport::eTopAccount: - m_group = "topaccount,account"; + m_group << ctTopAccount << ctAccount; break; case MyMoneyReport::eAccount: - m_group = "account"; + m_group << ctAccount; break; case MyMoneyReport::eAccountReconcile: - m_group = "account,reconcileflag"; + m_group << ctAccount << ctReconcileFlag; break; case MyMoneyReport::ePayee: - m_group = "payee"; + m_group << ctPayee; break; case MyMoneyReport::eTag: - m_group = "tag"; + m_group << ctTag; break; case MyMoneyReport::eMonth: - m_group = "month"; + m_group << ctMonth; break; case MyMoneyReport::eWeek: - m_group = "week"; + m_group << ctWeek; break; case MyMoneyReport::eAccountByTopAccount: - m_group = "topaccount"; + m_group << ctTopAccount; break; case MyMoneyReport::eEquityType: - m_group = "equitytype"; + m_group << ctEquityType; break; case MyMoneyReport::eAccountType: - m_group = "type"; + m_group << ctType; break; case MyMoneyReport::eInstitution: - m_group = "institution,topaccount"; + m_group << ctInstitution << ctTopAccount; break; default: throw MYMONEYEXCEPTION("QueryTable::QueryTable(): unhandled row type"); } - QString sort = m_group + ",id,rank," + m_columns; + QVector sort = QVector::fromList(m_group) << QVector::fromList(m_columns) << ctID << ctRank; + m_columns.clear(); switch (m_config.rowType()) { case MyMoneyReport::eAccountByTopAccount: case MyMoneyReport::eEquityType: case MyMoneyReport::eAccountType: case MyMoneyReport::eInstitution: - m_columns = "account"; + m_columns << ctAccount; break; default: - m_columns = "postdate"; + m_columns << ctPostDate; } unsigned qc = m_config.queryColumns(); if (qc & MyMoneyReport::eQCnumber) - m_columns += ",number"; + m_columns << ctNumber; if (qc & MyMoneyReport::eQCpayee) - m_columns += ",payee"; + m_columns << ctPayee; if (qc & MyMoneyReport::eQCtag) - m_columns += ",tag"; + m_columns << ctTag; if (qc & MyMoneyReport::eQCcategory) - m_columns += ",category"; + m_columns << ctCategory; if (qc & MyMoneyReport::eQCaccount) - m_columns += ",account"; + m_columns << ctAccount; if (qc & MyMoneyReport::eQCreconciled) - m_columns += ",reconcileflag"; + m_columns << ctReconcileFlag; if (qc & MyMoneyReport::eQCmemo) - m_columns += ",memo"; + m_columns << ctMemo; if (qc & MyMoneyReport::eQCaction) - m_columns += ",action"; + m_columns << ctAction; if (qc & MyMoneyReport::eQCshares) - m_columns += ",shares"; + m_columns << ctShares; if (qc & MyMoneyReport::eQCprice) - m_columns += ",price"; + m_columns << ctPrice; if (qc & MyMoneyReport::eQCperformance) { + m_subtotal.clear(); switch (m_config.investmentSum()) { - case MyMoneyReport::eSumOwnedAndSold: - m_columns += ",buys,sells,reinvestincome,cashincome,endingbal,return,returninvestment"; - m_subtotal = "buys,sells,reinvestincome,cashincome,endingbal,return,returninvestment"; - break; - case MyMoneyReport::eSumOwned: - m_columns += ",buys,reinvestincome,marketvalue,return,returninvestment"; - m_subtotal = "buys,reinvestincome,marketvalue,return,returninvestment"; - break; - case MyMoneyReport::eSumSold: - m_columns += ",buys,sells,cashincome,return,returninvestment"; - m_subtotal = "buys,sells,cashincome,return,returninvestment"; - break; - case MyMoneyReport::eSumPeriod: - default: - m_columns += ",startingbal,buys,sells,reinvestincome,cashincome,endingbal,return,returninvestment"; - m_subtotal = "startingbal,buys,sells,reinvestincome,cashincome,endingbal,return,returninvestment"; - break; + case MyMoneyReport::eSumOwnedAndSold: + m_columns << ctBuys << ctSells << ctReinvestIncome << ctCashIncome + << ctEndingBalance << ctReturn << ctReturnInvestment; + m_subtotal << ctBuys << ctSells << ctReinvestIncome << ctCashIncome + << ctEndingBalance << ctReturn << ctReturnInvestment; + break; + case MyMoneyReport::eSumOwned: + m_columns << ctBuys << ctReinvestIncome << ctMarketValue + << ctReturn << ctReturnInvestment; + m_subtotal << ctBuys << ctReinvestIncome << ctMarketValue + << ctReturn << ctReturnInvestment; + break; + case MyMoneyReport::eSumSold: + m_columns << ctBuys << ctSells << ctCashIncome + << ctReturn << ctReturnInvestment; + m_subtotal << ctBuys << ctSells << ctCashIncome + << ctReturn << ctReturnInvestment; + break; + case MyMoneyReport::eSumPeriod: + default: + m_columns << ctStartingBalance << ctBuys << ctSells + << ctReinvestIncome << ctCashIncome << ctEndingBalance + << ctReturn << ctReturnInvestment; + m_subtotal << ctStartingBalance << ctBuys << ctSells + << ctReinvestIncome << ctCashIncome << ctEndingBalance + << ctReturn << ctReturnInvestment; + break; } } if (qc & MyMoneyReport::eQCcapitalgain) { + m_subtotal.clear(); switch (m_config.investmentSum()) { - case MyMoneyReport::eSumOwned: - m_columns += ",shares,buyprice,lastprice,buys,marketvalue,percentagegain,capitalgain"; - m_subtotal = "buys,marketvalue,percentagegain,capitalgain"; - break; - case MyMoneyReport::eSumSold: - default: - 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"; - } - break; + case MyMoneyReport::eSumOwned: + m_columns << ctShares << ctBuyPrice << ctLastPrice + << ctBuys << ctMarketValue << ctPercentageGain + << ctCapitalGain; + m_subtotal << ctShares << ctBuyPrice << ctLastPrice + << ctBuys << ctMarketValue << ctPercentageGain + << ctCapitalGain; + break; + case MyMoneyReport::eSumSold: + default: + m_columns << ctBuys << ctSells << ctCapitalGain; + m_subtotal << ctBuys << ctSells << ctCapitalGain; + if (m_config.isShowingSTLTCapitalGains()) { + m_columns << ctBuysST << ctSellsST << ctCapitalGainST + << ctBuysLT << ctSellsLT << ctCapitalGainLT; + m_subtotal << ctBuysST << ctSellsST << ctCapitalGainST + << ctBuysLT << ctSellsLT << ctCapitalGainLT; + } + break; } } if (qc & MyMoneyReport::eQCloan) { - m_columns += ",payment,interest,fees"; - m_postcolumns = "balance"; + m_columns << ctPayment << ctInterest << ctFees; + m_postcolumns << ctBalance; } if (qc & MyMoneyReport::eQCbalance) - m_postcolumns = "balance"; + m_postcolumns << ctBalance; TableRow::setSortCriteria(sort); qSort(m_rows); if (m_config.isShowingColumnTotals()) 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... + if (m_rows.at(0)[ctRank] == QLatin1String("4") || m_rows.at(0)[ctRank] == QLatin1String("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(','); + QList subtotals = m_subtotal; + QList groups = m_group; + QList columns = m_columns; if (!m_subtotal.isEmpty() && subtotals.count() == 1) columns.append(m_subtotal); - QStringList postcolumns = m_postcolumns.split(','); + QList postcolumns = m_postcolumns; if (!m_postcolumns.isEmpty()) columns.append(postcolumns); - QMap>> totalCurrency; - QList> totalGroups; - QMap totalsValues; + 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()); + totalsValues.insert(ctRowsCount, 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")) { + while (iNextRow != m_rows.count() && (m_rows.at(iNextRow).value(ctRank) == QLatin1String("4") || m_rows.at(iNextRow).value(ctRank) == QLatin1String("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) + QString currencyID = m_rows.at(iCurrentRow).value(ctCurrency); + if (m_rows.at(iCurrentRow).value(ctRank) == QLatin1String("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; + totalCurrency[currencyID].last()[ctRowsCount] += 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(); + 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 + if (!MyMoneyMoney((*currencyGrp).at(i + 1).value(ctRowsCount)).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(); + 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("reinvestincome"), (*currencyGrp).at(i + 1).value("sells"), - (*currencyGrp).at(i + 1).value("startingbal"), (*currencyGrp).at(i + 1).value("endingbal") + (*currencyGrp).at(i + 1).value("marketvalue"), - (*currencyGrp).at(i + 1).value("cashincome")).toString(); - else if (subtotal == "percentagegain") - totalsRow[subtotal] = (((*currencyGrp).at(i + 1).value("buys") + (*currencyGrp).at(i + 1).value("marketvalue")) / (*currencyGrp).at(i + 1).value("buys").abs()).toString(); - else if (subtotal == "price") - totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(i + 1).value("price") / (*currencyGrp).at(i + 1).value("rows_count")).toString(); + if (subtotal == ctReturnInvestment) + totalsRow[subtotal] = helperROI((*currencyGrp).at(i + 1).value(ctBuys) - (*currencyGrp).at(i + 1).value(ctReinvestIncome), (*currencyGrp).at(i + 1).value(ctSells), + (*currencyGrp).at(i + 1).value(ctStartingBalance), (*currencyGrp).at(i + 1).value(ctEndingBalance) + (*currencyGrp).at(i + 1).value(ctMarketValue), + (*currencyGrp).at(i + 1).value(ctCashIncome)).toString(); + else if (subtotal == ctPercentageGain) + totalsRow[subtotal] = (((*currencyGrp).at(i + 1).value(ctBuys) + (*currencyGrp).at(i + 1).value(ctMarketValue)) / (*currencyGrp).at(i + 1).value(ctBuys).abs()).toString(); + else if (subtotal == ctPrice) + totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(i + 1).value(ctPrice) / (*currencyGrp).at(i + 1).value(ctRowsCount)).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) + if (stashedTotalRows.at(j).value(ctCurrency) != currencyID) continue; foreach (auto subtotal, subtotals) { - if (subtotal == "return") - totalsRow["return"] = stashedTotalRows.takeAt(j)["return"]; + if (subtotal == ctReturn) + totalsRow[ctReturn] = stashedTotalRows.takeAt(j)[ctReturn]; } 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; + totalsRow[ctCurrency] = currencyID; if (isMainCurrencyTotal) { - totalsRow["rank"] = "4"; + totalsRow[ctRank] = QLatin1Char('4'); isMainCurrencyTotal = false; } else - totalsRow["rank"] = "5"; - totalsRow["depth"] = QString::number(i); - totalsRow.remove("rows_count"); + totalsRow[ctRank] = QLatin1Char('5'); + totalsRow[ctDepth] = QString::number(i); + totalsRow.remove(ctRowsCount); 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(); + QMap>>::iterator currencyGrp = totalCurrency.begin(); while (currencyGrp != totalCurrency.end()) { TableRow totalsRow; - QMap::const_iterator grandTotalGrp = (*currencyGrp)[0].constBegin(); + 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("reinvestincome"), (*currencyGrp).at(0).value("sells"), - (*currencyGrp).at(0).value("startingbal"), (*currencyGrp).at(0).value("endingbal") + (*currencyGrp).at(0).value("marketvalue"), - (*currencyGrp).at(0).value("cashincome")).toString(); - else if (subtotal == "percentagegain") - totalsRow[subtotal] = (((*currencyGrp).at(0).value("buys") + (*currencyGrp).at(0).value("marketvalue")) / (*currencyGrp).at(0).value("buys").abs()).toString(); - else if (subtotal == "price") - totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(0).value("price") / (*currencyGrp).at(0).value("rows_count")).toString(); + if (subtotal == ctReturnInvestment) + totalsRow[subtotal] = helperROI((*currencyGrp).at(0).value(ctBuys) - (*currencyGrp).at(0).value(ctReinvestIncome), (*currencyGrp).at(0).value(ctSells), + (*currencyGrp).at(0).value(ctStartingBalance), (*currencyGrp).at(0).value(ctEndingBalance) + (*currencyGrp).at(0).value(ctMarketValue), + (*currencyGrp).at(0).value(ctCashIncome)).toString(); + else if (subtotal == ctPercentageGain) + totalsRow[subtotal] = (((*currencyGrp).at(0).value(ctBuys) + (*currencyGrp).at(0).value(ctMarketValue)) / (*currencyGrp).at(0).value(ctBuys).abs()).toString(); + else if (subtotal == ctPrice) + totalsRow[subtotal] = MyMoneyMoney((*currencyGrp).at(0).value(ctPrice) / (*currencyGrp).at(0).value(ctRowsCount)).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"]; + if (subtotal == ctReturn) + totalsRow[ctReturn] = stashedTotalRows.takeAt(j)[ctReturn]; } } } 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; + totalsRow[ctCurrency] = currencyID; if (isMainCurrencyTotal) { - totalsRow["rank"] = "4"; + totalsRow[ctRank] = QLatin1Char('4'); isMainCurrencyTotal = false; } else - totalsRow["rank"] = "5"; - totalsRow["depth"] = ""; + totalsRow[ctRank] = QLatin1Char('5'); + totalsRow[ctDepth] = QString(); 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(); + qA[ctID] = qS[ctID] = (* it_transaction).id(); + qA[ctEntryDate] = qS[ctEntryDate] = (* it_transaction).entryDate().toString(Qt::ISODate); + qA[ctPostDate] = qS[ctPostDate] = (* it_transaction).postDate().toString(Qt::ISODate); + qA[ctCommodity] = qS[ctCommodity] = (* it_transaction).commodity(); pd = (* it_transaction).postDate(); - qA["month"] = qS["month"] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); - qA["week"] = qS["week"] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); + qA[ctMonth] = qS[ctMonth] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); + qA[ctWeek] = qS[ctWeek] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); if (report.isConvertCurrency()) - qA["currency"] = qS["currency"] = file->baseCurrency().id(); + qA[ctCurrency] = qS[ctCurrency] = file->baseCurrency().id(); else - qA["currency"] = qS["currency"] = (*it_transaction).commodity(); + qA[ctCurrency] = qS[ctCurrency] = (*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 = ""; + 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; + qA[ctCurrency] = qS[ctCurrency] = baseCurrency; } else - qA["currency"] = qS["currency"] = myBeginCurrency; // ...and set information about non-baseCurrency + qA[ctCurrency] = qS[ctCurrency] = 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(); + qA[ctAccount] = splitAcc.name(); + qA[ctAccountID] = splitAcc.id(); + qA[ctTopAccount] = splitAcc.topParentName(); if (splitAcc.isInvest()) { // use the institution of the parent for stock accounts institution = splitAcc.parent().institutionId(); MyMoneyMoney shares = (*it_split).shares(); int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision(); - qA["action"] = (*it_split).action(); - qA["shares"] = shares.isZero() ? "" : shares.toString(); - qA["price"] = shares.isZero() ? "" : xr.convertPrecision(pricePrecision).toString(); + qA[ctAction] = (*it_split).action(); + qA[ctShares] = shares.isZero() ? QString() : shares.toString(); + qA[ctPrice] = shares.isZero() ? QString() : xr.convertPrecision(pricePrecision).toString(); if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && shares.isNegative()) - qA["action"] = "Sell"; + qA[ctAction] = "Sell"; - qA["investaccount"] = splitAcc.parent().name(); + qA[ctInvestAccount] = 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; + qA[ctCurrency] = qS[ctCurrency] = baseCurrency; } else - qA["currency"] = qS["currency"] = myBeginCurrency; + qA[ctCurrency] = qS[ctCurrency] = myBeginCurrency; } else xr = MyMoneyMoney::ONE; - qA["price"] = shares.isZero() ? "" : (stockSplit.price() * xr / (*it_split).price()).toString(); + qA[ctPrice] = shares.isZero() ? QString() : (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(); + qA[ctPrice] = xr.toString(); a_fullname = splitAcc.fullName(); a_memo = (*it_split).memo(); transaction_text = m_config.match(&(*it_split)); - qA["institution"] = institution.isEmpty() + qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); - qA["payee"] = payee.isEmpty() + qA[ctPayee] = 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 = ", "; + QString delimiter; + foreach(const auto tagId, tagIdList) { + qA[ctTag] += delimiter + file->tag(tagId).name().simplified(); + delimiter = QLatin1Char(','); } } - qA["reconciledate"] = (*it_split).reconcileDate().toString(Qt::ISODate); - qA["reconcileflag"] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true); - qA["number"] = (*it_split).number(); + qA[ctReconcileDate] = (*it_split).reconcileDate().toString(Qt::ISODate); + qA[ctReconcileFlag] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true); + qA[ctNumber] = (*it_split).number(); - qA["memo"] = a_memo; + qA[ctMemo] = a_memo; - qA["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); + qA[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString(); - qS["reconciledate"] = qA["reconciledate"]; - qS["reconcileflag"] = qA["reconcileflag"]; - qS["number"] = qA["number"]; + qS[ctReconcileDate] = qA[ctReconcileDate]; + qS[ctReconcileFlag] = qA[ctReconcileFlag]; + qS[ctNumber] = qA[ctNumber]; - qS["topcategory"] = splitAcc.topParentName(); - qS["categorytype"] = i18n("Transfer"); + qS[ctTopCategory] = splitAcc.topParentName(); + qS[ctCategoryType] = 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[ctValue] = (-(*it_split).shares() * xr).convert(fraction).toString(); - qA["rank"] = '1'; - qA["split"] = ""; + qA[ctRank] = QLatin1Char('1'); + qA[ctSplit].clear(); } 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"); + qA[ctRank] = QLatin1Char('1'); + qA[ctCategory] = i18n("[Split Transaction]"); + qA[ctTopCategory] = i18nc("Split transaction", "Split"); + qA[ctCategoryType] = 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(); + qA[ctPayee] = 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(); + qA[ctInterest] = 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(); + MyMoneyMoney n0 = MyMoneyMoney(qA[ctFees]); + qA[ctFees] = (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"] = ""; + qA[ctValue].clear(); //convert to lowest fraction - qA["split"] = (-(*it_split).shares() * xr).convert(fraction).toString(); - qA["rank"] = '2'; + qA[ctSplit] = (-(*it_split).shares() * xr).convert(fraction).toString(); + qA[ctRank] = QLatin1Char('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[ctSplit].clear(); + qA[ctRank] = QLatin1Char('1'); } - qA ["memo"] = (*it_split).memo(); + qA [ctMemo] = (*it_split).memo(); if (report.isConvertCurrency()) - qS["currency"] = file->baseCurrency().id(); + qS[ctCurrency] = file->baseCurrency().id(); else - qS["currency"] = splitAcc.currencyId(); + qS[ctCurrency] = splitAcc.currency().id(); if (! splitAcc.isIncomeExpense()) { - qA["category"] = ((*it_split).shares().isNegative()) ? + qA[ctCategory] = ((*it_split).shares().isNegative()) ? i18n("Transfer from %1", splitAcc.fullName()) : i18n("Transfer to %1", splitAcc.fullName()); - qA["topcategory"] = splitAcc.topParentName(); - qA["categorytype"] = i18n("Transfer"); + qA[ctTopCategory] = splitAcc.topParentName(); + qA[ctCategoryType] = i18n("Transfer"); } else { - qA ["category"] = splitAcc.fullName(); - qA ["topcategory"] = splitAcc.topParentName(); - qA ["categorytype"] = KMyMoneyUtils::accountTypeToString(splitAcc.accountGroup()); + qA [ctCategory] = splitAcc.fullName(); + qA [ctTopCategory] = splitAcc.topParentName(); + qA [ctCategoryType] = 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]"); + qA[ctTag] = i18n("[No Tag]"); else for (int i = 0; i < tagIdListCache.size(); i++) { - qA["tag"] = file->tag(tagIdListCache[i]).name().simplified(); + qA[ctTag] = 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[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString(); - qS["rank"] = '1'; + qS[ctRank] = QLatin1Char('1'); - qS["account"] = splitAcc.name(); - qS["accountid"] = splitAcc.id(); - qS["topaccount"] = splitAcc.topParentName(); + qS[ctAccount] = splitAcc.name(); + qS[ctAccountID] = splitAcc.id(); + qS[ctTopAccount] = splitAcc.topParentName(); - qS["category"] = ((*it_split).shares().isNegative()) + qS[ctCategory] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", a_fullname) : i18n("Transfer from %1", a_fullname); - qS["institution"] = institution.isEmpty() + qS[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); - qS["memo"] = (*it_split).memo().isEmpty() + qS[ctMemo] = (*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 = ""; + QString delimiter; for (int i = 0; i < tagIdList.size(); i++) { - qA["tag"] += delimiter + file->tag(tagIdList[i]).name().simplified(); - delimiter = '+'; + qA[ctTag] += delimiter + file->tag(tagIdList[i]).name().simplified(); + delimiter = "+"; } - qS["payee"] = payee.isEmpty() - ? qA["payee"] + qS[ctPayee] = payee.isEmpty() + ? qA[ctPayee] : 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(); + qA[ctCurrency] = file->baseCurrency().id(); else - qA["currency"] = account.currency().id(); + qA[ctCurrency] = 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[ctAccountID] = account.id(); + qA[ctAccount] = account.name(); + qA[ctTopAccount] = account.topParentName(); + qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); + qA[ctRank] = QLatin1Char('0'); - qA["price"] = startPrice.convertPrecision(account.currency().pricePrecision()).toString(); + qA[ctPrice] = startPrice.convertPrecision(account.currency().pricePrecision()).toString(); if (account.isInvest()) { - qA["shares"] = startShares.toString(); + qA[ctShares] = startShares.toString(); } - qA["postdate"] = strStartDate; - qA["balance"] = startBalance.convert(fraction).toString(); - qA["value"].clear(); - qA["id"] = 'A'; + qA[ctPostDate] = strStartDate; + qA[ctBalance] = startBalance.convert(fraction).toString(); + qA[ctValue].clear(); + qA[ctID] = QLatin1Char('A'); m_rows += qA; //ending balance - qA["price"] = endPrice.convertPrecision(account.currency().pricePrecision()).toString(); + qA[ctPrice] = endPrice.convertPrecision(account.currency().pricePrecision()).toString(); if (account.isInvest()) { - qA["shares"] = endShares.toString(); + qA[ctShares] = endShares.toString(); } - qA["postdate"] = strEndDate; - qA["balance"] = endBalance.toString(); - qA["rank"] = "3"; - qA["id"] = 'Z'; + qA[ctPostDate] = strEndDate; + qA[ctBalance] = endBalance.toString(); + qA[ctRank] = QLatin1Char('3'); + qA[ctID] = QLatin1Char('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::sumInvestmentValues(const ReportAccount& account, QList& cfList, QList& shList) const { for (int i = InvestmentValue::Buys; i < InvestmentValue::End; ++i) cfList.append(CashFlowList()); for (int i = InvestmentValue::Buys; i <= InvestmentValue::BuysOfOwned; ++i) shList.append(MyMoneyMoney()); MyMoneyFile* file = MyMoneyFile::instance(); 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); newStartingDate = startingDate; newEndingDate = endingDate; if (report.queryColumns() & MyMoneyReport::eQCcapitalgain) { // Saturday and Sunday aren't valid settlement dates if (endingDate.dayOfWeek() == Qt::Saturday) endingDate = endingDate.addDays(-1); else if (endingDate.dayOfWeek() == Qt::Sunday) endingDate = endingDate.addDays(-2); if (termSeparator.dayOfWeek() == Qt::Saturday) termSeparator = termSeparator.addDays(-1); else if (termSeparator.dayOfWeek() == Qt::Sunday) termSeparator = termSeparator.addDays(-2); if (startingDate.daysTo(endingDate) <= settlementPeriod) // no days to check for return; termSeparator = termSeparator.addDays(-settlementPeriod); newEndingDate = endingDate.addDays(-settlementPeriod); } shList[BuysOfOwned] = file->balance(account.id(), newEndingDate); // get how many shares there are at the end of period MyMoneyMoney stashedBuysOfOwned = shList.at(BuysOfOwned); 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 security; MyMoneySecurity currency; MyMoneySplit::investTransactionTypeE transactionType; KMyMoneyUtils::dissectTransaction((*it_t), shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType); QDate postDate = (*it_t).postDate(); MyMoneyMoney price; //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(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 (reportedDateRange) { cfList[Buys].append(CashFlowListItem(postDate, value)); shList[Buys] += shares; } if (shList.at(BuysOfOwned).isZero()) { // add sold shares if (shList.at(BuysOfSells) + shares > shList.at(Sells).abs()) { // add partially sold shares MyMoneyMoney tempVal = (((shList.at(Sells).abs() - shList.at(BuysOfSells))) / shares) * value; cfList[BuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[BuysOfSells] = shList.at(Sells).abs(); if (isSTLT && postDate < termSeparator) { cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[LongTermBuysOfSells] = shList.at(BuysOfSells); } } else { // add wholly sold shares cfList[BuysOfSells].append(CashFlowListItem(postDate, value)); shList[BuysOfSells] += shares; if (isSTLT && postDate < termSeparator) { cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, value)); shList[LongTermBuysOfSells] += shares; } } } else if (shList.at(BuysOfOwned) >= shares) { // substract not-sold shares shList[BuysOfOwned] -= shares; cfList[BuysOfOwned].append(CashFlowListItem(postDate, value)); } else { // substract partially not-sold shares MyMoneyMoney tempVal = ((shares - shList.at(BuysOfOwned)) / shares) * value; MyMoneyMoney tempVal2 = (shares - shList.at(BuysOfOwned)); cfList[BuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[BuysOfSells] += tempVal2; if (isSTLT && postDate < termSeparator) { cfList[LongTermBuysOfSells].append(CashFlowListItem(postDate, tempVal)); shList[LongTermBuysOfSells] += tempVal2; } cfList[BuysOfOwned].append(CashFlowListItem(postDate, (shList.at(BuysOfOwned) / shares) * value)); shList[BuysOfOwned] = MyMoneyMoney(); } } else if (transactionType == MyMoneySplit::SellShares && reportedDateRange) { cfList[Sells].append(CashFlowListItem(postDate, value)); shList[Sells] += shares; } else if (transactionType == MyMoneySplit::SplitShares) { // shares variable is denominator of split ratio here for (int i = Buys; i <= InvestmentValue::BuysOfOwned; ++i) shList[i] /= shares; } else if (transactionType == MyMoneySplit::AddShares || // added shares, when sold give 100% capital gain transactionType == MyMoneySplit::ReinvestDividend) { if (shList.at(BuysOfOwned).isZero()) { // add added/reinvested shares if (shList.at(BuysOfSells) + shares > shList.at(Sells).abs()) { // add partially added/reinvested shares shList[BuysOfSells] = shList.at(Sells).abs(); if (postDate < termSeparator) shList[LongTermBuysOfSells] = shList[BuysOfSells]; } else { // add wholly added/reinvested shares shList[BuysOfSells] += shares; if (postDate < termSeparator) shList[LongTermBuysOfSells] += shares; } } else if (shList.at(BuysOfOwned) >= shares) { // substract not-added/not-reinvested shares shList[BuysOfOwned] -= shares; cfList[BuysOfOwned].append(CashFlowListItem(postDate, value)); } else { // substract partially not-added/not-reinvested shares MyMoneyMoney tempVal = (shares - shList.at(BuysOfOwned)); shList[BuysOfSells] += tempVal; if (postDate < termSeparator) shList[LongTermBuysOfSells] += tempVal; cfList[BuysOfOwned].append(CashFlowListItem(postDate, (shList.at(BuysOfOwned) / shares) * value)); shList[BuysOfOwned] = MyMoneyMoney(); } - qDebug() << "szeres: " << shares.toDouble() << "prais: " << price.toDouble() << "value:" << value.toDouble(); if (transactionType == MyMoneySplit::ReinvestDividend) { value = MyMoneyMoney(); foreach (const auto split, interestSplits) value += split.value(); value *= price; cfList[ReinvestIncome].append(CashFlowListItem(postDate, -value)); } } else if (transactionType == MyMoneySplit::RemoveShares && reportedDateRange) // removed shares give no value in return so no capital gain on them shList[Sells] += shares; else if (transactionType == MyMoneySplit::Dividend || transactionType == MyMoneySplit::Yield) cfList[CashIncome].append(CashFlowListItem(postDate, value)); } reportedDateRange = false; newEndingDate = newStartingDate; newStartingDate = newStartingDate.addYears(-1); report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier } while ( ( (report.investmentSum() == MyMoneyReport::eSumOwned && !shList[BuysOfOwned].isZero()) || (report.investmentSum() == MyMoneyReport::eSumSold && !shList.at(Sells).isZero() && shList.at(Sells).abs() > shList.at(BuysOfSells).abs()) || (report.investmentSum() == MyMoneyReport::eSumOwnedAndSold && (!shList[BuysOfOwned].isZero() || (!shList.at(Sells).isZero() && shList.at(Sells).abs() > shList.at(BuysOfSells).abs()))) ) && account.openingDate() <= newEndingDate ); // we've got buy value and no sell value of long-term shares, so get them if (isSTLT && !shList[LongTermBuysOfSells].isZero()) { newStartingDate = startingDate; newEndingDate = endingDate.addDays(-settlementPeriod); report.setDateFilter(newStartingDate, newEndingDate); // search for matching buy transactions year earlier QList transactions = file->transactionList(report); shList[BuysOfOwned] = shList[LongTermBuysOfSells]; foreach (const auto transaction, transactions) { MyMoneySplit shareSplit = transaction.splitByAccount(account.id()); MyMoneySplit assetAccountSplit; QList feeSplits; QList interestSplits; MyMoneySecurity security; MyMoneySecurity currency; MyMoneySplit::investTransactionTypeE transactionType; KMyMoneyUtils::dissectTransaction(transaction, shareSplit, assetAccountSplit, feeSplits, interestSplits, security, currency, transactionType); QDate postDate = transaction.postDate(); MyMoneyMoney price; if (m_config.isConvertCurrency()) price = account.baseCurrencyPrice(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 ((shList.at(LongTermSellsOfBuys) + shares).abs() >= shList.at(LongTermBuysOfSells)) { // add partially sold long-term shares cfList[LongTermSellsOfBuys].append(CashFlowListItem(postDate, (shList.at(LongTermSellsOfBuys).abs() - shList.at(LongTermBuysOfSells)) / shares * value)); shList[LongTermSellsOfBuys] = shList.at(LongTermBuysOfSells); break; } else { // add wholly sold long-term shares cfList[LongTermSellsOfBuys].append(CashFlowListItem(postDate, value)); shList[LongTermSellsOfBuys] += shares; } } else if (transactionType == MyMoneySplit::RemoveShares) { if ((shList.at(LongTermSellsOfBuys) + shares).abs() >= shList.at(LongTermBuysOfSells)) { shList[LongTermSellsOfBuys] = shList.at(LongTermBuysOfSells); break; } else shList[LongTermSellsOfBuys] += shares; } } } shList[BuysOfOwned] = stashedBuysOfOwned; report.setDateFilter(startingDate, endingDate); // reset data filter for next security return; } void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result, CashFlowList &all) const { MyMoneyReport report = m_config; QDate startingDate; QDate endingDate; report.validDateRange(startingDate, endingDate); startingDate = startingDate.addDays(-1); MyMoneyFile* file = MyMoneyFile::instance(); //get fraction depending on type of account int fraction = account.currency().smallestAccountFraction(); MyMoneyMoney price; 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); QList cfList; QList shList; sumInvestmentValues(account, cfList, shList); MyMoneyMoney buysTotal; MyMoneyMoney sellsTotal; MyMoneyMoney cashIncomeTotal; MyMoneyMoney reinvestIncomeTotal; switch (m_config.investmentSum()) { case MyMoneyReport::eSumOwnedAndSold: buysTotal = cfList.at(BuysOfSells).total() + cfList.at(BuysOfOwned).total(); sellsTotal = cfList.at(Sells).total(); cashIncomeTotal = cfList.at(CashIncome).total(); reinvestIncomeTotal = cfList.at(ReinvestIncome).total(); startingBal = MyMoneyMoney(); if (buysTotal.isZero() && sellsTotal.isZero() && cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero()) return; all.append(cfList.at(BuysOfSells)); all.append(cfList.at(BuysOfOwned)); all.append(cfList.at(Sells)); all.append(cfList.at(CashIncome)); - result[QLatin1String("sells")] = sellsTotal.toString(); - result[QLatin1String("cashincome")] = cashIncomeTotal.toString(); - result[QLatin1String("reinvestincome")] = reinvestIncomeTotal.toString(); - result[QLatin1String("endingbal")] = endingBal.toString(); + result[ctSells] = sellsTotal.toString(); + result[ctCashIncome] = cashIncomeTotal.toString(); + result[ctReinvestIncome] = reinvestIncomeTotal.toString(); + result[ctEndingBalance] = endingBal.toString(); break; case MyMoneyReport::eSumOwned: buysTotal = cfList.at(BuysOfOwned).total(); startingBal = MyMoneyMoney(); if (buysTotal.isZero() && endingBal.isZero()) return; all.append(cfList.at(BuysOfOwned)); all.append(CashFlowListItem(endingDate, endingBal)); - result[QLatin1String("reinvestincome")] = reinvestIncomeTotal.toString(); - result[QLatin1String("marketvalue")] = endingBal.toString(); + result[ctReinvestIncome] = reinvestIncomeTotal.toString(); + result[ctMarketValue] = endingBal.toString(); break; case MyMoneyReport::eSumSold: buysTotal = cfList.at(BuysOfSells).total(); sellsTotal = cfList.at(Sells).total(); cashIncomeTotal = cfList.at(CashIncome).total(); startingBal = endingBal = MyMoneyMoney(); // check if there are any meaningfull values before adding them to results if (buysTotal.isZero() && sellsTotal.isZero() && cashIncomeTotal.isZero()) return; all.append(cfList.at(BuysOfSells)); all.append(cfList.at(Sells)); all.append(cfList.at(CashIncome)); - result[QLatin1String("sells")] = sellsTotal.toString(); - result[QLatin1String("cashincome")] = cashIncomeTotal.toString(); + result[ctSells] = sellsTotal.toString(); + result[ctCashIncome] = cashIncomeTotal.toString(); break; case MyMoneyReport::eSumPeriod: default: buysTotal = cfList.at(Buys).total(); sellsTotal = cfList.at(Sells).total(); cashIncomeTotal = cfList.at(CashIncome).total(); reinvestIncomeTotal = cfList.at(ReinvestIncome).total(); if (buysTotal.isZero() && sellsTotal.isZero() && cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero() && startingBal.isZero() && endingBal.isZero()) return; all.append(cfList.at(Buys)); all.append(cfList.at(Sells)); all.append(cfList.at(CashIncome)); all.append(CashFlowListItem(startingDate, -startingBal)); all.append(CashFlowListItem(endingDate, endingBal)); - result[QLatin1String("sells")] = sellsTotal.toString(); - result[QLatin1String("cashincome")] = cashIncomeTotal.toString(); - result[QLatin1String("reinvestincome")] = reinvestIncomeTotal.toString(); - result[QLatin1String("startingbal")] = startingBal.toString(); - result[QLatin1String("endingbal")] = endingBal.toString(); + result[ctSells] = sellsTotal.toString(); + result[ctCashIncome] = cashIncomeTotal.toString(); + result[ctReinvestIncome] = reinvestIncomeTotal.toString(); + result[ctStartingBalance] = startingBal.toString(); + result[ctEndingBalance] = endingBal.toString(); break; } MyMoneyMoney returnInvestment = helperROI(buysTotal - reinvestIncomeTotal, sellsTotal, startingBal, endingBal, cashIncomeTotal); MyMoneyMoney annualReturn = helperIRR(all); - result[QLatin1String("buys")] = buysTotal.toString(); - result[QLatin1String("return")] = annualReturn.toString(); - result[QLatin1String("returninvestment")] = returnInvestment.toString(); - result[QLatin1String("equitytype")] = KMyMoneyUtils::securityTypeToString(file->security(account.currencyId()).securityType()); + result[ctBuys] = buysTotal.toString(); + result[ctReturn] = annualReturn.toString(); + result[ctReturnInvestment] = returnInvestment.toString(); + result[ctEquityType] = KMyMoneyUtils::securityTypeToString(file->security(account.currencyId()).securityType()); } void QueryTable::constructCapitalGainRow(const ReportAccount& account, TableRow& result) const { MyMoneyFile* file = MyMoneyFile::instance(); QList cfList; QList shList; sumInvestmentValues(account, cfList, shList); MyMoneyMoney buysTotal = cfList.at(BuysOfSells).total(); MyMoneyMoney sellsTotal = cfList.at(Sells).total(); MyMoneyMoney longTermBuysOfSellsTotal = cfList.at(LongTermBuysOfSells).total(); MyMoneyMoney longTermSellsOfBuys = cfList.at(LongTermSellsOfBuys).total(); switch (m_config.investmentSum()) { case MyMoneyReport::eSumOwned: { if (shList.at(BuysOfOwned).isZero()) return; MyMoneyReport report = m_config; QDate startingDate; QDate endingDate; report.validDateRange(startingDate, endingDate); //get fraction depending on type of account int fraction = account.currency().smallestAccountFraction(); MyMoneyMoney price; //calculate ending balance if (m_config.isConvertCurrency()) price = account.deepCurrencyPrice(endingDate) * account.baseCurrencyPrice(endingDate); else price = account.deepCurrencyPrice(endingDate); MyMoneyMoney endingBal = shList.at(BuysOfOwned) * price; //convert to lowest fraction endingBal = endingBal.convert(fraction); buysTotal = cfList.at(BuysOfOwned).total() - cfList.at(ReinvestIncome).total(); int pricePrecision = file->security(account.currencyId()).pricePrecision(); - result[QLatin1String("buys")] = buysTotal.toString(); - result[QLatin1String("shares")] = shList.at(BuysOfOwned).toString(); - result[QLatin1String("buyprice")] = (buysTotal.abs() / shList.at(BuysOfOwned)).convertPrecision(pricePrecision).toString(); - result[QLatin1String("lastprice")] = price.toString(); - result[QLatin1String("marketvalue")] = endingBal.toString(); - result[QLatin1String("capitalgain")] = (buysTotal + endingBal).toString(); - result[QLatin1String("percentagegain")] = ((buysTotal + endingBal)/buysTotal.abs()).toString(); + result[ctBuys] = buysTotal.toString(); + result[ctShares] = shList.at(BuysOfOwned).toString(); + result[ctBuyPrice] = (buysTotal.abs() / shList.at(BuysOfOwned)).convertPrecision(pricePrecision).toString(); + result[ctLastPrice] = price.toString(); + result[ctMarketValue] = endingBal.toString(); + result[ctCapitalGain] = (buysTotal + endingBal).toString(); + result[ctPercentageGain] = ((buysTotal + endingBal)/buysTotal.abs()).toString(); break; } case MyMoneyReport::eSumSold: default: buysTotal = cfList.at(BuysOfSells).total() - cfList.at(ReinvestIncome).total(); sellsTotal = cfList.at(Sells).total(); longTermBuysOfSellsTotal = cfList.at(LongTermBuysOfSells).total(); longTermSellsOfBuys = cfList.at(LongTermSellsOfBuys).total(); // check if there are any meaningfull values before adding them to results if (buysTotal.isZero() && sellsTotal.isZero() && longTermBuysOfSellsTotal.isZero() && longTermSellsOfBuys.isZero()) return; - result[QLatin1String("buys")] = buysTotal.toString(); - result[QLatin1String("sells")] = sellsTotal.toString(); - result[QLatin1String("capitalgain")] = (buysTotal + sellsTotal).toString(); + result[ctBuys] = buysTotal.toString(); + result[ctSells] = sellsTotal.toString(); + result[ctCapitalGain] = (buysTotal + sellsTotal).toString(); if (m_config.isShowingSTLTCapitalGains()) { - result[QLatin1String("buysLT")] = longTermBuysOfSellsTotal.toString(); - result[QLatin1String("sellsLT")] = longTermSellsOfBuys.toString(); - result[QLatin1String("capitalgainLT")] = (longTermBuysOfSellsTotal + longTermSellsOfBuys).toString(); - result[QLatin1String("buysST")] = (buysTotal - longTermBuysOfSellsTotal).toString(); - result[QLatin1String("sellsST")] = (sellsTotal - longTermSellsOfBuys).toString(); - result[QLatin1String("capitalgainST")] = ((buysTotal - longTermBuysOfSellsTotal) + (sellsTotal - longTermSellsOfBuys)).toString(); + result[ctBuysLT] = longTermBuysOfSellsTotal.toString(); + result[ctSellsLT] = longTermSellsOfBuys.toString(); + result[ctCapitalGainLT] = (longTermBuysOfSellsTotal + longTermSellsOfBuys).toString(); + result[ctBuysST] = (buysTotal - longTermBuysOfSellsTotal).toString(); + result[ctSellsST] = (sellsTotal - longTermSellsOfBuys).toString(); + result[ctCapitalGainST] = ((buysTotal - longTermBuysOfSellsTotal) + (sellsTotal - longTermSellsOfBuys)).toString(); } break; } - result[QLatin1String("equitytype")] = KMyMoneyUtils::securityTypeToString(file->security(account.currencyId()).securityType()); + result[ctEquityType] = KMyMoneyUtils::securityTypeToString(file->security(account.currencyId()).securityType()); } 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(); + qaccountrow[ctTopAccount] = account.topParentName(); if (m_config.isConvertCurrency()) - qaccountrow["currency"] = file->baseCurrency().id(); + qaccountrow[ctCurrency] = file->baseCurrency().id(); else - qaccountrow["currency"] = account.currency().id(); + qaccountrow[ctCurrency] = 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... + if (!currencyCashFlow.value(qaccountrow.value(ctCurrency)).contains(qaccountrow.value(ctTopAccount))) + currencyCashFlow[qaccountrow.value(ctCurrency)].insert(qaccountrow.value(ctTopAccount), accountCashflow); // create cashflow for unknown account... else - currencyCashFlow[qaccountrow.value("currency")][qaccountrow.value("topaccount")] += accountCashflow; // ...or add cashflow for known account + currencyCashFlow[qaccountrow.value(ctCurrency)][qaccountrow.value(ctTopAccount)] += 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(); int pricePrecision = file->security(account.currencyId()).pricePrecision(); - qaccountrow["price"] = netprice.convertPrecision(pricePrecision).toString(); - qaccountrow["value"] = (netprice * shares).convert(fraction).toString(); - qaccountrow["shares"] = shares.toString(); + qaccountrow[ctPrice] = netprice.convertPrecision(pricePrecision).toString(); + qaccountrow[ctValue] = (netprice * shares).convert(fraction).toString(); + qaccountrow[ctShares] = 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"); + qaccountrow[ctInstitution] = i18nc("No institution", "None"); else - qaccountrow["institution"] = file->institution(iid).name(); + qaccountrow[ctInstitution] = file->institution(iid).name(); - qaccountrow["type"] = KMyMoneyUtils::accountTypeToString(account.accountType()); + qaccountrow[ctType] = 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(); + qaccountrow[ctRank] = QLatin1Char('1'); + qaccountrow[ctAccount] = account.name(); + qaccountrow[ctAccountID] = account.id(); + qaccountrow[ctTopAccount] = account.topParentName(); if (m_config.isConvertCurrency()) - qaccountrow["currency"] = file->baseCurrency().id(); + qaccountrow[ctCurrency] = file->baseCurrency().id(); else - qaccountrow["currency"] = account.currency().id(); + qaccountrow[ctCurrency] = account.currency().id(); m_rows.append(qaccountrow); } } if (m_config.queryColumns() == MyMoneyReport::eQCperformance && m_config.isShowingColumnTotals()) { TableRow qtotalsrow; - qtotalsrow["rank"] = "4"; // add identification of row as total + qtotalsrow[ctRank] = QLatin1Char('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(); + qtotalsrow[ctTopAccount] = topAccount.key(); + qtotalsrow[ctReturn] = helperIRR(topAccount.value()).toString(); + qtotalsrow[ctCurrency] = 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 + qtotalsrow[ctTopAccount].clear(); // empty topaccount because it's grand cashflow while (currencyGrp != currencyGrandCashFlow.end()) { - qtotalsrow["return"] = helperIRR(currencyGrp.value()).toString(); - qtotalsrow["currency"] = currencyGrp.key(); + qtotalsrow[ctReturn] = helperIRR(currencyGrp.value()).toString(); + qtotalsrow[ctCurrency] = 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(); + qA[ctID] = qS[ctID] = (* it_transaction).id(); + qA[ctEntryDate] = qS[ctEntryDate] = (* it_transaction).entryDate().toString(Qt::ISODate); + qA[ctPostDate] = qS[ctPostDate] = (* it_transaction).postDate().toString(Qt::ISODate); + qA[ctCommodity] = qS[ctCommodity] = (* it_transaction).commodity(); pd = (* it_transaction).postDate(); - qA["month"] = qS["month"] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); - qA["week"] = qS["week"] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); + qA[ctMonth] = qS[ctMonth] = i18n("Month of %1", QDate(pd.year(), pd.month(), 1).toString(Qt::ISODate)); + qA[ctWeek] = qS[ctWeek] = i18n("Week of %1", pd.addDays(1 - pd.dayOfWeek()).toString(Qt::ISODate)); if (report.isConvertCurrency()) - qA["currency"] = qS["currency"] = file->baseCurrency().id(); + qA[ctCurrency] = qS[ctCurrency] = file->baseCurrency().id(); else - qA["currency"] = qS["currency"] = (*it_transaction).commodity(); + qA[ctCurrency] = qS[ctCurrency] = (*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 = ""; + 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(); int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision(); - qA["action"] = (*it_split).action(); - qA["shares"] = shares.isZero() ? "" : (*it_split).shares().toString(); - qA["price"] = shares.isZero() ? "" : xr.convertPrecision(pricePrecision).toString(); + qA[ctAction] = (*it_split).action(); + qA[ctShares] = shares.isZero() ? QString() : (*it_split).shares().toString(); + qA[ctPrice] = shares.isZero() ? QString() : xr.convertPrecision(pricePrecision).toString(); if (((*it_split).action() == MyMoneySplit::ActionBuyShares) && (*it_split).shares().isNegative()) - qA["action"] = "Sell"; + qA[ctAction] = "Sell"; - qA["investaccount"] = splitAcc.parent().name(); + qA[ctInvestAccount] = splitAcc.parent().name(); } include_me = m_config.includes(splitAcc); a_fullname = splitAcc.fullName(); a_memo = (*it_split).memo(); int pricePrecision = file->security(splitAcc.currencyId()).pricePrecision(); - qA["price"] = xr.convertPrecision(pricePrecision).toString(); - qA["account"] = splitAcc.name(); - qA["accountid"] = splitAcc.id(); - qA["topaccount"] = splitAcc.topParentName(); + qA[ctPrice] = xr.convertPrecision(pricePrecision).toString(); + qA[ctAccount] = splitAcc.name(); + qA[ctAccountID] = splitAcc.id(); + qA[ctTopAccount] = splitAcc.topParentName(); - qA["institution"] = institution.isEmpty() + qA[ctInstitution] = 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 = ','; + QString delimiter; + foreach(const auto tagId, tagIdList) { + qA[ctTag] += delimiter + file->tag(tagId).name().simplified(); + delimiter = QLatin1Char(','); } - qA["payee"] = payee.isEmpty() + qA[ctPayee] = 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[ctReconcileDate] = (*it_split).reconcileDate().toString(Qt::ISODate); + qA[ctReconcileFlag] = KMyMoneyUtils::reconcileStateToString((*it_split).reconcileFlag(), true); + qA[ctNumber] = (*it_split).number(); - qA["memo"] = a_memo; + qA[ctMemo] = a_memo; - qS["reconciledate"] = qA["reconciledate"]; - qS["reconcileflag"] = qA["reconcileflag"]; - qS["number"] = qA["number"]; + qS[ctReconcileDate] = qA[ctReconcileDate]; + qS[ctReconcileFlag] = qA[ctReconcileFlag]; + qS[ctNumber] = qA[ctNumber]; - qS["topcategory"] = splitAcc.topParentName(); + qS[ctTopCategory] = 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'; + qA[ctValue] = ((*it_split).shares() * xr).convert(fraction).toString(); + qA[ctRank] = QLatin1Char('1'); //fill in account information if (! splitAcc.isIncomeExpense() && it_split != myBegin) { - qA["account"] = ((*it_split).shares().isNegative()) ? + qA[ctAccount] = ((*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]"); + qA[ctAccount] = 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()) ? + qA[ctAccount] = ((*it_split).shares().isNegative()) ? i18n("Transfer to %1", tempSplitAcc.fullName()) : i18n("Transfer from %1", tempSplitAcc.fullName()); } else { - qA["account"] = tempSplitAcc.fullName(); + qA[ctAccount] = tempSplitAcc.fullName(); } } } else { //in any other case, fill in the account name of the main split - qA["account"] = myBeginAcc.fullName(); + qA[ctAccount] = 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()); + qA [ctCategory] = splitAcc.fullName(); + qA [ctTopCategory] = splitAcc.topParentName(); + qA [ctCategoryType] = 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(); + qA[ctCurrency] = file->baseCurrency().id(); else - qA["currency"] = account.currency().id(); + qA[ctCurrency] = 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[ctAccountID] = account.id(); + qA[ctAccount] = account.name(); + qA[ctTopAccount] = account.topParentName(); + qA[ctInstitution] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); + qA[ctRank] = QLatin1Char('0'); int pricePrecision = file->security(account.currencyId()).pricePrecision(); - qA["price"] = startPrice.convertPrecision(pricePrecision).toString(); + qA[ctPrice] = startPrice.convertPrecision(pricePrecision).toString(); if (account.isInvest()) { - qA["shares"] = startShares.toString(); + qA[ctShares] = startShares.toString(); } - qA["postdate"] = strStartDate; - qA["balance"] = startBalance.convert(fraction).toString(); - qA["value"].clear(); - qA["id"] = 'A'; + qA[ctPostDate] = strStartDate; + qA[ctBalance] = startBalance.convert(fraction).toString(); + qA[ctValue].clear(); + qA[ctID] = QLatin1Char('A'); m_rows += qA; - qA["rank"] = "3"; + qA[ctRank] = QLatin1Char('3'); //ending balance - qA["price"] = endPrice.convertPrecision(pricePrecision).toString(); + qA[ctPrice] = endPrice.convertPrecision(pricePrecision).toString(); if (account.isInvest()) { - qA["shares"] = endShares.toString(); + qA[ctShares] = endShares.toString(); } - qA["postdate"] = strEndDate; - qA["balance"] = endBalance.toString(); - qA["id"] = 'Z'; + qA[ctPostDate] = strEndDate; + qA[ctBalance] = endBalance.toString(); + qA[ctID] = QLatin1Char('Z'); m_rows += qA; } } } diff --git a/kmymoney/reports/tests/querytable-test.cpp b/kmymoney/reports/tests/querytable-test.cpp index 50e04316e..c01f71cf7 100644 --- a/kmymoney/reports/tests/querytable-test.cpp +++ b/kmymoney/reports/tests/querytable-test.cpp @@ -1,801 +1,801 @@ /*************************************************************************** querytabletest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net 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 "querytable-test.h" #include #include #include #include "reportstestcommon.h" #include "querytable.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneystoragedump.h" #include "mymoneyreport.h" #include "mymoneystatement.h" #include "mymoneystoragexml.h" using namespace reports; using namespace test; QTEST_GUILESS_MAIN(QueryTableTest) void QueryTableTest::init() { storage = new MyMoneySeqAccessMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Thomas Baumgart"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount(QString("Checking Account"), MyMoneyAccount::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset); acCredit = makeAccount(QString("Credit Card"), MyMoneyAccount::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability); acSolo = makeAccount(QString("Solo"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acParent = makeAccount(QString("Parent"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acChild = makeAccount(QString("Child"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acForeign = makeAccount(QString("Foreign"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acTax = makeAccount(QString("Tax"), MyMoneyAccount::Expense, MyMoneyMoney(), QDate(2005, 1, 11), acExpense, "", true); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void QueryTableTest::cleanup() { file->detachStorage(storage); delete storage; } void QueryTableTest::testQueryBasics() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eCategory); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; filter.setQueryColumns(static_cast(cols)); // filter.setName("Transactions by Category"); XMLandback(filter); QueryTable qtbl_1(filter); writeTabletoHTML(qtbl_1, "Transactions by Category.html"); QList rows = qtbl_1.rows(); QVERIFY(rows.count() == 19); - QVERIFY(rows[0]["categorytype"] == "Expense"); - QVERIFY(rows[0]["category"] == "Parent"); - QVERIFY(rows[0]["postdate"] == "2004-02-01"); - QVERIFY(rows[14]["categorytype"] == "Expense"); - QVERIFY(rows[14]["category"] == "Solo"); - QVERIFY(rows[14]["postdate"] == "2005-01-01"); - - QVERIFY(MyMoneyMoney(rows[6]["value"]) == -(moParent1 + moParent2) * 3); - QVERIFY(MyMoneyMoney(rows[10]["value"]) == -(moChild) * 3); - QVERIFY(MyMoneyMoney(rows[16]["value"]) == -(moSolo) * 3); - QVERIFY(MyMoneyMoney(rows[17]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); - QVERIFY(MyMoneyMoney(rows[18]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); + QVERIFY(rows[0][ListTable::ctCategoryType] == "Expense"); + QVERIFY(rows[0][ListTable::ctCategory] == "Parent"); + QVERIFY(rows[0][ListTable::ctPostDate] == "2004-02-01"); + QVERIFY(rows[14][ListTable::ctCategoryType] == "Expense"); + QVERIFY(rows[14][ListTable::ctCategory] == "Solo"); + QVERIFY(rows[14][ListTable::ctPostDate] == "2005-01-01"); + + QVERIFY(MyMoneyMoney(rows[6][ListTable::ctValue]) == -(moParent1 + moParent2) * 3); + QVERIFY(MyMoneyMoney(rows[10][ListTable::ctValue]) == -(moChild) * 3); + QVERIFY(MyMoneyMoney(rows[16][ListTable::ctValue]) == -(moSolo) * 3); + QVERIFY(MyMoneyMoney(rows[17][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); + QVERIFY(MyMoneyMoney(rows[18][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eTopCategory); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; filter.setQueryColumns(static_cast(cols)); // filter.setName("Transactions by Top Category"); XMLandback(filter); QueryTable qtbl_2(filter); writeTabletoHTML(qtbl_2, "Transactions by Top Category.html"); rows = qtbl_2.rows(); QVERIFY(rows.count() == 16); - QVERIFY(rows[0]["categorytype"] == "Expense"); - QVERIFY(rows[0]["topcategory"] == "Parent"); - QVERIFY(rows[0]["postdate"] == "2004-02-01"); - QVERIFY(rows[7]["categorytype"] == "Expense"); - QVERIFY(rows[7]["topcategory"] == "Parent"); - QVERIFY(rows[7]["postdate"] == "2005-09-01"); - QVERIFY(rows[12]["categorytype"] == "Expense"); - QVERIFY(rows[12]["topcategory"] == "Solo"); - QVERIFY(rows[12]["postdate"] == "2005-01-01"); - - QVERIFY(MyMoneyMoney(rows[9]["value"]) == -(moParent1 + moParent2 + moChild) * 3); - QVERIFY(MyMoneyMoney(rows[13]["value"]) == -(moSolo) * 3); - QVERIFY(MyMoneyMoney(rows[14]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); - QVERIFY(MyMoneyMoney(rows[15]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); + QVERIFY(rows[0][ListTable::ctCategoryType] == "Expense"); + QVERIFY(rows[0][ListTable::ctTopCategory] == "Parent"); + QVERIFY(rows[0][ListTable::ctPostDate] == "2004-02-01"); + QVERIFY(rows[8][ListTable::ctCategoryType] == "Expense"); + QVERIFY(rows[8][ListTable::ctTopCategory] == "Parent"); + QVERIFY(rows[8][ListTable::ctPostDate] == "2005-09-01"); + QVERIFY(rows[12][ListTable::ctCategoryType] == "Expense"); + QVERIFY(rows[12][ListTable::ctTopCategory] == "Solo"); + QVERIFY(rows[12][ListTable::ctPostDate] == "2005-01-01"); + + QVERIFY(MyMoneyMoney(rows[9][ListTable::ctValue]) == -(moParent1 + moParent2 + moChild) * 3); + QVERIFY(MyMoneyMoney(rows[13][ListTable::ctValue]) == -(moSolo) * 3); + QVERIFY(MyMoneyMoney(rows[14][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); + QVERIFY(MyMoneyMoney(rows[15][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account.html"); rows = qtbl_3.rows(); #if 1 QVERIFY(rows.count() == 19); - QVERIFY(rows[1]["account"] == "Checking Account"); - QVERIFY(rows[1]["category"] == "Solo"); - QVERIFY(rows[1]["postdate"] == "2004-01-01"); - QVERIFY(rows[14]["account"] == "Credit Card"); - QVERIFY(rows[14]["category"] == "Parent"); - QVERIFY(rows[14]["postdate"] == "2005-09-01"); + QVERIFY(rows[1][ListTable::ctAccount] == "Checking Account"); + QVERIFY(rows[1][ListTable::ctCategory] == "Solo"); + QVERIFY(rows[1][ListTable::ctPostDate] == "2004-01-01"); + QVERIFY(rows[15][ListTable::ctAccount] == "Credit Card"); + QVERIFY(rows[15][ListTable::ctCategory] == "Parent"); + QVERIFY(rows[15][ListTable::ctPostDate] == "2005-09-01"); #else QVERIFY(rows.count() == 12); - QVERIFY(rows[0]["account"] == "Checking Account"); - QVERIFY(rows[0]["category"] == "Solo"); - QVERIFY(rows[0]["postdate"] == "2004-01-01"); - QVERIFY(rows[11]["account"] == "Credit Card"); - QVERIFY(rows[11]["category"] == "Parent"); - QVERIFY(rows[11]["postdate"] == "2005-09-01"); + QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); + QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); + QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); + QVERIFY(rows[11][ListTable::ctAccount] == "Credit Card"); + QVERIFY(rows[11][ListTable::ctCategory] == "Parent"); + QVERIFY(rows[11][ListTable::ctPostDate] == "2005-09-01"); #endif - QVERIFY(MyMoneyMoney(rows[5]["value"]) == -(moSolo) * 3 + moCheckingOpen); - QVERIFY(MyMoneyMoney(rows[17]["value"]) == -(moParent1 + moParent2 + moChild) * 3 + moCreditOpen); - QVERIFY(MyMoneyMoney(rows[18]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); + QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == -(moSolo) * 3 + moCheckingOpen); + QVERIFY(MyMoneyMoney(rows[17][ListTable::ctValue]) == -(moParent1 + moParent2 + moChild) * 3 + moCreditOpen); + QVERIFY(MyMoneyMoney(rows[18][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::ePayee); filter.setName("Transactions by Payee"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCmemo | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_4(filter); writeTabletoHTML(qtbl_4, "Transactions by Payee.html"); rows = qtbl_4.rows(); QVERIFY(rows.count() == 14); - QVERIFY(rows[0]["payee"] == "Test Payee"); - QVERIFY(rows[0]["category"] == "Solo"); - QVERIFY(rows[0]["postdate"] == "2004-01-01"); - QVERIFY(rows[7]["payee"] == "Test Payee"); - QVERIFY(rows[7]["category"] == "Parent: Child"); - QVERIFY(rows[7]["postdate"] == "2004-11-07"); - QVERIFY(rows[10]["payee"] == "Test Payee"); - QVERIFY(rows[10]["category"] == "Parent"); - QVERIFY(rows[10]["postdate"] == "2005-09-01"); - - QVERIFY(MyMoneyMoney(rows[12]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); - QVERIFY(MyMoneyMoney(rows[13]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); + QVERIFY(rows[0][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); + QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); + QVERIFY(rows[7][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[7][ListTable::ctCategory] == "Parent: Child"); + QVERIFY(rows[7][ListTable::ctPostDate] == "2004-11-07"); + QVERIFY(rows[11][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[11][ListTable::ctCategory] == "Parent"); + QVERIFY(rows[11][ListTable::ctPostDate] == "2005-09-01"); + + QVERIFY(MyMoneyMoney(rows[12][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); + QVERIFY(MyMoneyMoney(rows[13][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eMonth); filter.setName("Transactions by Month"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_5(filter); writeTabletoHTML(qtbl_5, "Transactions by Month.html"); rows = qtbl_5.rows(); QVERIFY(rows.count() == 23); - QVERIFY(rows[0]["payee"] == "Test Payee"); - QVERIFY(rows[0]["category"] == "Solo"); - QVERIFY(rows[0]["postdate"] == "2004-01-01"); - QVERIFY(rows[12]["payee"] == "Test Payee"); - QVERIFY(rows[12]["category"] == "Parent: Child"); - QVERIFY(rows[12]["postdate"] == "2004-11-07"); - QVERIFY(rows[20]["payee"] == "Test Payee"); - QVERIFY(rows[20]["category"] == "Parent"); - QVERIFY(rows[20]["postdate"] == "2005-09-01"); - - QVERIFY(MyMoneyMoney(rows[1]["value"]) == -moSolo); - QVERIFY(MyMoneyMoney(rows[15]["value"]) == -(moChild) * 3); - QVERIFY(MyMoneyMoney(rows[9]["value"]) == -moParent1 + moCheckingOpen); - QVERIFY(MyMoneyMoney(rows[22]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); + QVERIFY(rows[0][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); + QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); + QVERIFY(rows[12][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[12][ListTable::ctCategory] == "Parent: Child"); + QVERIFY(rows[12][ListTable::ctPostDate] == "2004-11-07"); + QVERIFY(rows[20][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[20][ListTable::ctCategory] == "Parent"); + QVERIFY(rows[20][ListTable::ctPostDate] == "2005-09-01"); + + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == -moSolo); + QVERIFY(MyMoneyMoney(rows[15][ListTable::ctValue]) == -(moChild) * 3); + QVERIFY(MyMoneyMoney(rows[9][ListTable::ctValue]) == -moParent1 + moCheckingOpen); + QVERIFY(MyMoneyMoney(rows[22][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eWeek); filter.setName("Transactions by Week"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_6(filter); writeTabletoHTML(qtbl_6, "Transactions by Week.html"); rows = qtbl_6.rows(); QVERIFY(rows.count() == 23); - QVERIFY(rows[0]["payee"] == "Test Payee"); - QVERIFY(rows[0]["category"] == "Solo"); - QVERIFY(rows[0]["postdate"] == "2004-01-01"); - QVERIFY(rows[20]["payee"] == "Test Payee"); - QVERIFY(rows[20]["category"] == "Parent"); - QVERIFY(rows[20]["postdate"] == "2005-09-01"); - - QVERIFY(MyMoneyMoney(rows[1]["value"]) == -moSolo); - QVERIFY(MyMoneyMoney(rows[15]["value"]) == -(moChild) * 3); - QVERIFY(MyMoneyMoney(rows[21]["value"]) == -moParent2); - QVERIFY(MyMoneyMoney(rows[22]["value"]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); + QVERIFY(rows[0][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); + QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); + QVERIFY(rows[20][ListTable::ctPayee] == "Test Payee"); + QVERIFY(rows[20][ListTable::ctCategory] == "Parent"); + QVERIFY(rows[20][ListTable::ctPostDate] == "2005-09-01"); + + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == -moSolo); + QVERIFY(MyMoneyMoney(rows[15][ListTable::ctValue]) == -(moChild) * 3); + QVERIFY(MyMoneyMoney(rows[21][ListTable::ctValue]) == -moParent2); + QVERIFY(MyMoneyMoney(rows[22][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } // Test querytable::TableRow::operator> and operator== QueryTable::TableRow low; - low["first"] = 'A'; - low["second"] = 'B'; - low["third"] = 'C'; + low[ListTable::ctPrice] = 'A'; + low[ListTable::ctLastPrice] = 'B'; + low[ListTable::ctBuyPrice] = 'C'; QueryTable::TableRow high; - high["first"] = 'A'; - high["second"] = 'C'; - high["third"] = 'B'; + high[ListTable::ctPrice] = 'A'; + high[ListTable::ctLastPrice] = 'C'; + high[ListTable::ctBuyPrice] = 'B'; - QueryTable::TableRow::setSortCriteria("first,second,third"); + QueryTable::TableRow::setSortCriteria({ListTable::ctPrice, ListTable::ctLastPrice, ListTable::ctBuyPrice}); QVERIFY(low < high); QVERIFY(low <= high); QVERIFY(high > low); QVERIFY(high <= high); QVERIFY(high == high); } void QueryTableTest::testCashFlowAnalysis() { // // Test IRR calculations // CashFlowList list; list += CashFlowListItem(QDate(2004, 5, 3), MyMoneyMoney(1000.0)); list += CashFlowListItem(QDate(2004, 5, 20), MyMoneyMoney(59.0)); list += CashFlowListItem(QDate(2004, 6, 3), MyMoneyMoney(14.0)); list += CashFlowListItem(QDate(2004, 6, 24), MyMoneyMoney(92.0)); list += CashFlowListItem(QDate(2004, 7, 6), MyMoneyMoney(63.0)); list += CashFlowListItem(QDate(2004, 7, 25), MyMoneyMoney(15.0)); list += CashFlowListItem(QDate(2004, 8, 5), MyMoneyMoney(92.0)); list += CashFlowListItem(QDate(2004, 9, 2), MyMoneyMoney(18.0)); list += CashFlowListItem(QDate(2004, 9, 21), MyMoneyMoney(5.0)); list += CashFlowListItem(QDate(2004, 10, 16), MyMoneyMoney(-2037.0)); MyMoneyMoney IRR(list.IRR(), 1000); QVERIFY(IRR == MyMoneyMoney(1676, 1000)); list.pop_back(); list += CashFlowListItem(QDate(2004, 10, 16), MyMoneyMoney(-1358.0)); IRR = MyMoneyMoney(list.IRR(), 1000); QVERIFY(IRR.isZero()); } void QueryTableTest::testAccountQuery() { try { QString htmlcontext = QString("\n\n%1\n\n"); // // No transactions, opening balances only // MyMoneyReport filter; filter.setRowType(MyMoneyReport::eInstitution); filter.setName("Accounts by Institution (No transactions)"); XMLandback(filter); QueryTable qtbl_1(filter); writeTabletoHTML(qtbl_1, "Accounts by Institution (No transactions).html"); QList rows = qtbl_1.rows(); QVERIFY(rows.count() == 6); - QVERIFY(rows[0]["account"] == "Checking Account"); - QVERIFY(MyMoneyMoney(rows[0]["value"]) == moCheckingOpen); - QVERIFY(rows[0]["equitytype"].isEmpty()); - QVERIFY(rows[2]["account"] == "Credit Card"); - QVERIFY(MyMoneyMoney(rows[1]["value"]) == moCreditOpen); - QVERIFY(rows[2]["equitytype"].isEmpty()); + QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == moCheckingOpen); + QVERIFY(rows[0][ListTable::ctEquityType].isEmpty()); + QVERIFY(rows[2][ListTable::ctAccount] == "Credit Card"); + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == moCreditOpen); + QVERIFY(rows[2][ListTable::ctEquityType].isEmpty()); - QVERIFY(MyMoneyMoney(rows[4]["value"]) == moCheckingOpen + moCreditOpen); - QVERIFY(MyMoneyMoney(rows[5]["value"]) == moCheckingOpen + moCreditOpen); + QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == moCheckingOpen + moCreditOpen); + QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == moCheckingOpen + moCreditOpen); // // Adding in transactions // TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); filter.setRowType(MyMoneyReport::eInstitution); filter.setName("Accounts by Institution (With Transactions)"); XMLandback(filter); QueryTable qtbl_2(filter); rows = qtbl_2.rows(); QVERIFY(rows.count() == 6); - QVERIFY(rows[0]["account"] == "Checking Account"); - QVERIFY(MyMoneyMoney(rows[0]["value"]) == (moCheckingOpen - moSolo*3)); - QVERIFY(rows[2]["account"] == "Credit Card"); - QVERIFY(MyMoneyMoney(rows[2]["value"]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); + QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == (moCheckingOpen - moSolo*3)); + QVERIFY(rows[2][ListTable::ctAccount] == "Credit Card"); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); - QVERIFY(MyMoneyMoney(rows[5]["value"]) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); + QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); // // Account TYPES // filter.setRowType(MyMoneyReport::eAccountType); filter.setName("Accounts by Type"); XMLandback(filter); QueryTable qtbl_3(filter); rows = qtbl_3.rows(); QVERIFY(rows.count() == 5); - QVERIFY(rows[0]["account"] == "Checking Account"); - QVERIFY(MyMoneyMoney(rows[0]["value"]) == (moCheckingOpen - moSolo * 3)); - QVERIFY(rows[2]["account"] == "Credit Card"); - QVERIFY(MyMoneyMoney(rows[2]["value"]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); - - QVERIFY(MyMoneyMoney(rows[1]["value"]) == moCheckingOpen - moSolo * 3); - QVERIFY(MyMoneyMoney(rows[3]["value"]) == moCreditOpen - (moParent1 + moParent2 + moChild) * 3); - QVERIFY(MyMoneyMoney(rows[4]["value"]) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); + QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == (moCheckingOpen - moSolo * 3)); + QVERIFY(rows[2][ListTable::ctAccount] == "Credit Card"); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); + + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == moCheckingOpen - moSolo * 3); + QVERIFY(MyMoneyMoney(rows[3][ListTable::ctValue]) == moCreditOpen - (moParent1 + moParent2 + moChild) * 3); + QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testInvestment() { try { // Equities eqStock1 = makeEquity("Stock1", "STK1"); eqStock2 = makeEquity("Stock2", "STK2"); eqStock3 = makeEquity("Stock3", "STK3"); eqStock4 = makeEquity("Stock4", "STK4"); // Accounts acInvestment = makeAccount("Investment", MyMoneyAccount::Investment, moZero, QDate(2003, 11, 1), acAsset); acStock1 = makeAccount("Stock 1", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1); acStock2 = makeAccount("Stock 2", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2); acStock3 = makeAccount("Stock 3", MyMoneyAccount::Stock, moZero, QDate(2003, 11, 1), acInvestment, eqStock3); acStock4 = makeAccount("Stock 4", MyMoneyAccount::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock4); acDividends = makeAccount("Dividends", MyMoneyAccount::Income, moZero, QDate(2004, 1, 1), acIncome); acInterest = makeAccount("Interest", MyMoneyAccount::Income, moZero, QDate(2004, 1, 1), acIncome); acFees = makeAccount("Fees", MyMoneyAccount::Expense, moZero, QDate(2003, 11, 1), acExpense); // Transactions // Date Action Shares Price Stock Asset Income InvTransactionHelper s1b1(QDate(2003, 12, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock3, acChecking, QString()); InvTransactionHelper s1b2(QDate(2004, 1, 30), MyMoneySplit::ActionBuyShares, MyMoneyMoney(500.00), MyMoneyMoney(100.00), acStock4, acChecking, acFees, MyMoneyMoney(100.00)); InvTransactionHelper s1b3(QDate(2004, 1, 30), MyMoneySplit::ActionBuyShares, MyMoneyMoney(500.00), MyMoneyMoney(90.00), acStock4, acChecking, acFees, MyMoneyMoney(100.00)); InvTransactionHelper s1b4(QDate(2004, 2, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1b5(QDate(2004, 3, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(1000.00), MyMoneyMoney(110.00), acStock1, acChecking, QString()); InvTransactionHelper s1s1(QDate(2004, 4, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(120.00), acStock1, acChecking, QString()); InvTransactionHelper s1s2(QDate(2004, 5, 1), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-200.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1s3(QDate(2004, 5, 30), MyMoneySplit::ActionBuyShares, MyMoneyMoney(-1000.00), MyMoneyMoney(120.00), acStock4, acChecking, acFees, MyMoneyMoney(200.00)); InvTransactionHelper s1r1(QDate(2004, 6, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(100.00), acStock1, QString(), acDividends); InvTransactionHelper s1r2(QDate(2004, 7, 1), MyMoneySplit::ActionReinvestDividend, MyMoneyMoney(50.00), MyMoneyMoney(80.00), acStock1, QString(), acDividends); InvTransactionHelper s1c1(QDate(2004, 8, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(100.00), acStock1, acChecking, acDividends); InvTransactionHelper s1c2(QDate(2004, 9, 1), MyMoneySplit::ActionDividend, MyMoneyMoney(10.00), MyMoneyMoney(120.00), acStock1, acChecking, acDividends); InvTransactionHelper s1y1(QDate(2004, 9, 15), MyMoneySplit::ActionYield, MyMoneyMoney(10.00), MyMoneyMoney(110.00), acStock1, acChecking, acInterest); makeEquityPrice(eqStock1, QDate(2004, 10, 1), MyMoneyMoney(100.00)); makeEquityPrice(eqStock3, QDate(2004, 10, 1), MyMoneyMoney(110.00)); makeEquityPrice(eqStock4, QDate(2004, 10, 1), MyMoneyMoney(110.00)); // // Investment Transactions Report // MyMoneyReport invtran_r( MyMoneyReport::eTopAccount, MyMoneyReport::eQCaction | MyMoneyReport::eQCshares | MyMoneyReport::eQCprice, MyMoneyTransactionFilter::userDefined, MyMoneyReport::eDetailAll, i18n("Investment Transactions"), i18n("Test Report") ); invtran_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 12, 31)); invtran_r.setInvestmentsOnly(true); XMLandback(invtran_r); QueryTable invtran(invtran_r); #if 1 writeTabletoHTML(invtran, "investment_transactions_test.html"); QList rows = invtran.rows(); QVERIFY(rows.count() == 32); - QVERIFY(MyMoneyMoney(rows[1]["value"]) == MyMoneyMoney(-100000.00)); - QVERIFY(MyMoneyMoney(rows[2]["value"]) == MyMoneyMoney(-110000.00)); - QVERIFY(MyMoneyMoney(rows[3]["value"]) == MyMoneyMoney(24000.00)); - QVERIFY(MyMoneyMoney(rows[4]["value"]) == MyMoneyMoney(20000.00)); - QVERIFY(MyMoneyMoney(rows[5]["value"]) == MyMoneyMoney(5000.00)); - QVERIFY(MyMoneyMoney(rows[6]["value"]) == MyMoneyMoney(4000.00)); - QVERIFY(MyMoneyMoney(rows[19]["value"]) == MyMoneyMoney(-50100.00)); - QVERIFY(MyMoneyMoney(rows[22]["value"]) == MyMoneyMoney(-45100.00)); + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == MyMoneyMoney(-100000.00)); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == MyMoneyMoney(-110000.00)); + QVERIFY(MyMoneyMoney(rows[3][ListTable::ctValue]) == MyMoneyMoney(24000.00)); + QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == MyMoneyMoney(20000.00)); + QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == MyMoneyMoney(5000.00)); + QVERIFY(MyMoneyMoney(rows[6][ListTable::ctValue]) == MyMoneyMoney(4000.00)); + QVERIFY(MyMoneyMoney(rows[19][ListTable::ctValue]) == MyMoneyMoney(-50100.00)); + QVERIFY(MyMoneyMoney(rows[22][ListTable::ctValue]) == MyMoneyMoney(-45100.00)); // need to fix these... fundamentally different from the original test - //QVERIFY(MyMoneyMoney(invtran.m_rows[8]["value"])==MyMoneyMoney( -1000.00)); - //QVERIFY(MyMoneyMoney(invtran.m_rows[11]["value"])==MyMoneyMoney( -1200.00)); - //QVERIFY(MyMoneyMoney(invtran.m_rows[14]["value"])==MyMoneyMoney( -1100.00)); - - QVERIFY(MyMoneyMoney(rows[1]["price"]) == MyMoneyMoney(100.00)); - QVERIFY(MyMoneyMoney(rows[3]["price"]) == MyMoneyMoney(120.00)); - QVERIFY(MyMoneyMoney(rows[5]["price"]) == MyMoneyMoney(100.00)); - QVERIFY(MyMoneyMoney(rows[7]["price"]) == MyMoneyMoney()); - QVERIFY(MyMoneyMoney(rows[10]["price"]) == MyMoneyMoney()); - QVERIFY(MyMoneyMoney(rows[19]["price"]) == MyMoneyMoney(100.00)); - QVERIFY(MyMoneyMoney(rows[22]["price"]) == MyMoneyMoney(90.00)); - - QVERIFY(MyMoneyMoney(rows[2]["shares"]) == MyMoneyMoney(1000.00)); - QVERIFY(MyMoneyMoney(rows[4]["shares"]) == MyMoneyMoney(-200.00)); - QVERIFY(MyMoneyMoney(rows[6]["shares"]) == MyMoneyMoney(50.00)); - QVERIFY(MyMoneyMoney(rows[8]["shares"]) == MyMoneyMoney(0.00)); - QVERIFY(MyMoneyMoney(rows[11]["shares"]) == MyMoneyMoney(0.00)); - QVERIFY(MyMoneyMoney(rows[19]["shares"]) == MyMoneyMoney(500.00)); - QVERIFY(MyMoneyMoney(rows[22]["shares"]) == MyMoneyMoney(500.00)); - - QVERIFY(rows[1]["action"] == "Buy"); - QVERIFY(rows[3]["action"] == "Sell"); - QVERIFY(rows[5]["action"] == "Reinvest"); - QVERIFY(rows[7]["action"] == "Dividend"); - QVERIFY(rows[13]["action"] == "Yield"); - QVERIFY(rows[19]["action"] == "Buy"); - QVERIFY(rows[22]["action"] == "Buy"); + //QVERIFY(MyMoneyMoney(invtran.m_rows[8][ListTable::ctValue])==MyMoneyMoney( -1000.00)); + //QVERIFY(MyMoneyMoney(invtran.m_rows[11][ListTable::ctValue])==MyMoneyMoney( -1200.00)); + //QVERIFY(MyMoneyMoney(invtran.m_rows[14][ListTable::ctValue])==MyMoneyMoney( -1100.00)); + + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctPrice]) == MyMoneyMoney(100.00)); + QVERIFY(MyMoneyMoney(rows[3][ListTable::ctPrice]) == MyMoneyMoney(120.00)); + QVERIFY(MyMoneyMoney(rows[5][ListTable::ctPrice]) == MyMoneyMoney(100.00)); + QVERIFY(MyMoneyMoney(rows[7][ListTable::ctPrice]) == MyMoneyMoney()); + QVERIFY(MyMoneyMoney(rows[10][ListTable::ctPrice]) == MyMoneyMoney()); + QVERIFY(MyMoneyMoney(rows[19][ListTable::ctPrice]) == MyMoneyMoney(100.00)); + QVERIFY(MyMoneyMoney(rows[22][ListTable::ctPrice]) == MyMoneyMoney(90.00)); + + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctShares]) == MyMoneyMoney(1000.00)); + QVERIFY(MyMoneyMoney(rows[4][ListTable::ctShares]) == MyMoneyMoney(-200.00)); + QVERIFY(MyMoneyMoney(rows[6][ListTable::ctShares]) == MyMoneyMoney(50.00)); + QVERIFY(MyMoneyMoney(rows[8][ListTable::ctShares]) == MyMoneyMoney(0.00)); + QVERIFY(MyMoneyMoney(rows[11][ListTable::ctShares]) == MyMoneyMoney(0.00)); + QVERIFY(MyMoneyMoney(rows[19][ListTable::ctShares]) == MyMoneyMoney(500.00)); + QVERIFY(MyMoneyMoney(rows[22][ListTable::ctShares]) == MyMoneyMoney(500.00)); + + QVERIFY(rows[1][ListTable::ctAction] == "Buy"); + QVERIFY(rows[3][ListTable::ctAction] == "Sell"); + QVERIFY(rows[5][ListTable::ctAction] == "Reinvest"); + QVERIFY(rows[7][ListTable::ctAction] == "Dividend"); + QVERIFY(rows[13][ListTable::ctAction] == "Yield"); + QVERIFY(rows[19][ListTable::ctAction] == "Buy"); + QVERIFY(rows[22][ListTable::ctAction] == "Buy"); #else QVERIFY(rows.count() == 9); - QVERIFY(MyMoneyMoney(rows[0]["value"]) == MyMoneyMoney(100000.00)); - QVERIFY(MyMoneyMoney(rows[1]["value"]) == MyMoneyMoney(110000.00)); - QVERIFY(MyMoneyMoney(rows[2]["value"]) == MyMoneyMoney(-24000.00)); - QVERIFY(MyMoneyMoney(rows[3]["value"]) == MyMoneyMoney(-20000.00)); - QVERIFY(MyMoneyMoney(rows[4]["value"]) == MyMoneyMoney(5000.00)); - QVERIFY(MyMoneyMoney(rows[5]["value"]) == MyMoneyMoney(4000.00)); - QVERIFY(MyMoneyMoney(rows[6]["value"]) == MyMoneyMoney(-1000.00)); - QVERIFY(MyMoneyMoney(rows[7]["value"]) == MyMoneyMoney(-1200.00)); - QVERIFY(MyMoneyMoney(rows[8]["value"]) == MyMoneyMoney(-1100.00)); - - QVERIFY(MyMoneyMoney(rows[0]["price"]) == MyMoneyMoney(100.00)); - QVERIFY(MyMoneyMoney(rows[2]["price"]) == MyMoneyMoney(120.00)); - QVERIFY(MyMoneyMoney(rows[4]["price"]) == MyMoneyMoney(100.00)); - QVERIFY(MyMoneyMoney(rows[6]["price"]) == MyMoneyMoney(0.00)); - QVERIFY(MyMoneyMoney(rows[8]["price"]) == MyMoneyMoney(0.00)); - - QVERIFY(MyMoneyMoney(rows[1]["shares"]) == MyMoneyMoney(1000.00)); - QVERIFY(MyMoneyMoney(rows[3]["shares"]) == MyMoneyMoney(-200.00)); - QVERIFY(MyMoneyMoney(rows[5]["shares"]) == MyMoneyMoney(50.00)); - QVERIFY(MyMoneyMoney(rows[7]["shares"]) == MyMoneyMoney(0.00)); - QVERIFY(MyMoneyMoney(rows[8]["shares"]) == MyMoneyMoney(0.00)); - - QVERIFY(rows[0]["action"] == "Buy"); - QVERIFY(rows[2]["action"] == "Sell"); - QVERIFY(rows[4]["action"] == "Reinvest"); - QVERIFY(rows[6]["action"] == "Dividend"); - QVERIFY(rows[8]["action"] == "Yield"); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == MyMoneyMoney(100000.00)); + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == MyMoneyMoney(110000.00)); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == MyMoneyMoney(-24000.00)); + QVERIFY(MyMoneyMoney(rows[3][ListTable::ctValue]) == MyMoneyMoney(-20000.00)); + QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == MyMoneyMoney(5000.00)); + QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == MyMoneyMoney(4000.00)); + QVERIFY(MyMoneyMoney(rows[6][ListTable::ctValue]) == MyMoneyMoney(-1000.00)); + QVERIFY(MyMoneyMoney(rows[7][ListTable::ctValue]) == MyMoneyMoney(-1200.00)); + QVERIFY(MyMoneyMoney(rows[8][ListTable::ctValue]) == MyMoneyMoney(-1100.00)); + + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctPrice]) == MyMoneyMoney(100.00)); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctPrice]) == MyMoneyMoney(120.00)); + QVERIFY(MyMoneyMoney(rows[4][ListTable::ctPrice]) == MyMoneyMoney(100.00)); + QVERIFY(MyMoneyMoney(rows[6][ListTable::ctPrice]) == MyMoneyMoney(0.00)); + QVERIFY(MyMoneyMoney(rows[8][ListTable::ctPrice]) == MyMoneyMoney(0.00)); + + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctShares]) == MyMoneyMoney(1000.00)); + QVERIFY(MyMoneyMoney(rows[3][ListTable::ctShares]) == MyMoneyMoney(-200.00)); + QVERIFY(MyMoneyMoney(rows[5][ListTable::ctShares]) == MyMoneyMoney(50.00)); + QVERIFY(MyMoneyMoney(rows[7][ListTable::ctShares]) == MyMoneyMoney(0.00)); + QVERIFY(MyMoneyMoney(rows[8][ListTable::ctShares]) == MyMoneyMoney(0.00)); + + QVERIFY(rows[0][ListTable::ctAction] == "Buy"); + QVERIFY(rows[2][ListTable::ctAction] == "Sell"); + QVERIFY(rows[4][ListTable::ctAction] == "Reinvest"); + QVERIFY(rows[6][ListTable::ctAction] == "Dividend"); + QVERIFY(rows[8][ListTable::ctAction] == "Yield"); #endif #if 1 // i think this is the correct amount. different treatment of dividend and yield - QVERIFY(MyMoneyMoney(rows[17]["value"]) == MyMoneyMoney(-153700.00)); - QVERIFY(MyMoneyMoney(rows[29]["value"]) == MyMoneyMoney(24600.00)); - QVERIFY(MyMoneyMoney(rows[31]["value"]) == MyMoneyMoney(-129100.00)); + QVERIFY(MyMoneyMoney(rows[17][ListTable::ctValue]) == MyMoneyMoney(-153700.00)); + QVERIFY(MyMoneyMoney(rows[29][ListTable::ctValue]) == MyMoneyMoney(24600.00)); + QVERIFY(MyMoneyMoney(rows[31][ListTable::ctValue]) == MyMoneyMoney(-129100.00)); #else QVERIFY(searchHTML(html, i18n("Total Stock 1")) == MyMoneyMoney(171700.00)); QVERIFY(searchHTML(html, i18n("Grand Total")) == MyMoneyMoney(171700.00)); #endif // // Investment Performance Report // MyMoneyReport invhold_r( MyMoneyReport::eAccountByTopAccount, MyMoneyReport::eQCperformance, MyMoneyTransactionFilter::userDefined, MyMoneyReport::eDetailAll, i18n("Investment Performance by Account"), i18n("Test Report") ); invhold_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 10, 1)); invhold_r.setInvestmentsOnly(true); XMLandback(invhold_r); QueryTable invhold(invhold_r); writeTabletoHTML(invhold, "Investment Performance by Account.html"); rows = invhold.rows(); QVERIFY(rows.count() == 5); - QVERIFY(MyMoneyMoney(rows[0]["return"]) == MyMoneyMoney("669/10000")); - QVERIFY(MyMoneyMoney(rows[0]["returninvestment"]) == MyMoneyMoney("-39/5000")); - QVERIFY(MyMoneyMoney(rows[0]["buys"]) == MyMoneyMoney(-210000.00)); - QVERIFY(MyMoneyMoney(rows[0]["sells"]) == MyMoneyMoney(44000.00)); - QVERIFY(MyMoneyMoney(rows[0]["reinvestincome"]) == MyMoneyMoney(9000.00)); - QVERIFY(MyMoneyMoney(rows[0]["cashincome"]) == MyMoneyMoney(3300.00)); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctReturn]) == MyMoneyMoney("669/10000")); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctReturnInvestment]) == MyMoneyMoney("-39/5000")); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctBuys]) == MyMoneyMoney(-210000.00)); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctSells]) == MyMoneyMoney(44000.00)); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctReinvestIncome]) == MyMoneyMoney(9000.00)); + QVERIFY(MyMoneyMoney(rows[0][ListTable::ctCashIncome]) == MyMoneyMoney(3300.00)); - QVERIFY(MyMoneyMoney(rows[1]["return"]) == MyMoneyMoney("1349/10000")); - QVERIFY(MyMoneyMoney(rows[1]["returninvestment"]) == MyMoneyMoney("1/10")); - QVERIFY(MyMoneyMoney(rows[1]["startingbal"]) == MyMoneyMoney(100000.00)); // this should stay non-zero to check if investment performance is calculated at non-zero starting balance + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctReturn]) == MyMoneyMoney("1349/10000")); + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctReturnInvestment]) == MyMoneyMoney("1/10")); + QVERIFY(MyMoneyMoney(rows[1][ListTable::ctStartingBalance]) == MyMoneyMoney(100000.00)); // this should stay non-zero to check if investment performance is calculated at non-zero starting balance - QVERIFY(MyMoneyMoney(rows[2]["return"]) == MyMoneyMoney("2501/2500")); - QVERIFY(MyMoneyMoney(rows[2]["returninvestment"]) == MyMoneyMoney("323/1250")); - QVERIFY(MyMoneyMoney(rows[2]["buys"]) == MyMoneyMoney(-95200.00)); - QVERIFY(MyMoneyMoney(rows[2]["sells"]) == MyMoneyMoney(119800.00)); - QVERIFY(MyMoneyMoney(rows[2]["endingbal"]) == MyMoneyMoney(0.00)); // this should stay zero to check if investment performance is calculated at zero ending balance + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctReturn]) == MyMoneyMoney("2501/2500")); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctReturnInvestment]) == MyMoneyMoney("323/1250")); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctBuys]) == MyMoneyMoney(-95200.00)); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctSells]) == MyMoneyMoney(119800.00)); + QVERIFY(MyMoneyMoney(rows[2][ListTable::ctEndingBalance]) == MyMoneyMoney(0.00)); // this should stay zero to check if investment performance is calculated at zero ending balance - QVERIFY(MyMoneyMoney(rows[4]["endingbal"]) == MyMoneyMoney(280000.00)); + QVERIFY(MyMoneyMoney(rows[4][ListTable::ctEndingBalance]) == MyMoneyMoney(280000.00)); #if 0 // Dump file & reports QFile g("investmentkmy.xml"); g.open(QIODevice::WriteOnly); MyMoneyStorageXML xml; IMyMoneyStorageFormat& interface = xml; interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); invtran.dump("invtran.html", "%1"); invhold.dump("invhold.html", "%1"); #endif } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } //this is to prevent me from making mistakes again when modifying balances - asoliverez //this case tests only the opening and ending balance of the accounts void QueryTableTest::testBalanceColumn() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::ActionWithdrawal, moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::ActionWithdrawal, moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::ActionWithdrawal, moChild, acCredit, acChild); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account.html"); QString html = qtbl_3.renderBody(); QList rows = qtbl_3.rows(); QVERIFY(rows.count() == 19); //this is to make sure that the dates of closing and opening balances and the balance numbers are ok QString openingDate = QLocale().toString(QDate(2004, 1, 1), QLocale::ShortFormat); QString closingDate = QLocale().toString(QDate(2005, 9, 1), QLocale::ShortFormat); QVERIFY(html.indexOf(openingDate + "" + i18n("Opening Balance")) > 0); QVERIFY(html.indexOf(closingDate + "" + i18n("Closing Balance") + " -702.36") > 0); QVERIFY(html.indexOf(closingDate + "" + i18n("Closing Balance") + " -705.69") > 0); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testBalanceColumnWithMultipleCurrencies() { try { MyMoneyMoney moJpyOpening(0.0, 1); MyMoneyMoney moJpyPrice(0.010, 100); MyMoneyMoney moJpyPrice2(0.011, 100); MyMoneyMoney moJpyPrice3(0.024, 100); MyMoneyMoney moTransaction(100, 1); MyMoneyMoney moJpyTransaction(100, 1); QString acJpyChecking = makeAccount(QString("Japanese Checking"), MyMoneyAccount::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY"); makePrice("JPY", QDate(2004, 1, 1), MyMoneyMoney(moJpyPrice)); makePrice("JPY", QDate(2004, 5, 1), MyMoneyMoney(moJpyPrice2)); makePrice("JPY", QDate(2004, 6, 30), MyMoneyMoney(moJpyPrice3)); QDate openingDate(2004, 2, 20); QDate intermediateDate(2004, 5, 20); QDate closingDate(2004, 7, 20); TransactionHelper t1(openingDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t4(openingDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking); TransactionHelper t2(intermediateDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t5(intermediateDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking); TransactionHelper t3(closingDate, MyMoneySplit::ActionTransfer, MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t6(closingDate, MyMoneySplit::ActionDeposit, MyMoneyMoney(moTransaction), acCredit, acChecking); // test that an income/expense transaction that involves a currency exchange is properly reported TransactionHelper t7(intermediateDate, MyMoneySplit::ActionWithdrawal, MyMoneyMoney(moJpyTransaction), acJpyChecking, acSolo, "JPY"); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance; filter.setQueryColumns(static_cast(cols)); // don't convert values to the default currency filter.setConvertCurrency(false); XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account (multiple currencies).html"); QString html = qtbl_3.renderBody(); QList rows = qtbl_3.rows(); QVERIFY(rows.count() == 24); //this is to make sure that the dates of closing and opening balances and the balance numbers are ok QString openingDateString = QLocale().toString(openingDate, QLocale::ShortFormat); QString intermediateDateString = QLocale().toString(intermediateDate, QLocale::ShortFormat); QString closingDateString = QLocale().toString(closingDate, QLocale::ShortFormat); // check the opening and closing balances QVERIFY(html.indexOf(openingDateString + "" + i18n("Opening Balance") + "USD 0.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + "USD 304.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + "USD -300.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + "JPY -400.00") > 0); // after a transfer of 100 JPY the balance should be 1.00 - price is 0.010 (precision of 2) QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer from Japanese CheckingUSD 1.00USD 1.00") > 0); // after a transfer of 100 the balance should be 101.00 QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer from Credit CardUSD 100.00USD 101.00") > 0); // after a transfer of 100 JPY the balance should be 102.00 - price is 0.011 (precision of 2) QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer from Japanese CheckingUSD 1.00USD 102.00") > 0); // after a transfer of 100 the balance should be 202.00 QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer from Credit CardUSD 100.00USD 202.00") > 0); // after a transfer of 100 JPY the balance should be 204.00 - price is 0.024 (precision of 2) QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer from Japanese CheckingUSD 2.00USD 204.00") > 0); // after a transfer of 100 the balance should be 304.00 QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer from Credit CardUSD 100.00USD 304.00") > 0); // a 100.00 JPY withdrawal should be displayed as such even if the expense account uses another currency - QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSoloJPY -100.00JPY -400.00") > 0); + QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSoloJPY -100.00JPY -300.00") > 0); // now run the same report again but this time convert all values to the base currency and make sure the values are correct filter.setConvertCurrency(true); XMLandback(filter); QueryTable qtbl_4(filter); writeTabletoHTML(qtbl_4, "Transactions by Account (multiple currencies converted to base).html"); html = qtbl_4.renderBody(); rows = qtbl_4.rows(); QVERIFY(rows.count() == 23); // check the opening and closing balances QVERIFY(html.indexOf(openingDateString + "" + i18n("Opening Balance") + " 0.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " 304.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " -300.00") > 0); // although the balance should be -5.00 it's -8.00 because the foreign currency balance is converted using the closing date price (0.024) QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " -8.00") > 0); // a 100.00 JPY transfer should be displayed as -1.00 when converted to the base currency using the opening date price QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer to Checking Account -1.00 -1.00") > 0); // a 100.00 JPY transfer should be displayed as -1.00 when converted to the base currency using the intermediate date price QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer to Checking Account -1.00 -2.00") > 0); // a 100.00 JPY transfer should be displayed as -2.00 when converted to the base currency using the closing date price (notice the balance is -5.00) - QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer to Checking Account -2.00 -4.00") > 0); + QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer to Checking Account -2.00 -5.00") > 0); // a 100.00 JPY withdrawal should be displayed as -1.00 when converted to the base currency using the intermediate date price - QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSolo -1.00 -5.00") > 0); + QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSolo -1.00 -3.00") > 0); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testTaxReport() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::ActionWithdrawal, moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::ActionWithdrawal, moParent1, acChecking, acTax); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eCategory); filter.setName("Tax Transactions"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; filter.setQueryColumns(static_cast(cols)); filter.setTax(true); XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Tax Transactions.html"); QList rows = qtbl_3.rows(); QString html = qtbl_3.renderBody(); QVERIFY(rows.count() == 5); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } }