diff --git a/kmymoney/plugins/views/reports/core/objectinfotable.cpp b/kmymoney/plugins/views/reports/core/objectinfotable.cpp index a5ae20c32..f6c65bdc6 100644 --- a/kmymoney/plugins/views/reports/core/objectinfotable.cpp +++ b/kmymoney/plugins/views/reports/core/objectinfotable.cpp @@ -1,388 +1,398 @@ /* * Copyright 2004-2005 Ace Jones * Copyright 2008-2010 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "objectinfotable.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneyaccountloan.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneypayee.h" #include "mymoneymoney.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyreport.h" #include "mymoneyschedule.h" #include "mymoneyexception.h" #include "kmymoneyutils.h" #include "reportaccount.h" #include "mymoneyenums.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 eMyMoney::Report::RowType::Schedule: constructScheduleTable(); m_columns << ctNextDueDate << ctName; break; default: qDebug() << "ObjectInfoTable::ObjectInfoTable(): unhandled row type" << static_cast(m_config.rowType()); qDebug() << "turned into eMyMoney::Report::RowType::AccountInfo"; m_config.setRowType(eMyMoney::Report::RowType::AccountInfo); // intentional fall through case eMyMoney::Report::RowType::AccountInfo: constructAccountTable(); m_columns << ctType << ctName; break; case eMyMoney::Report::RowType::AccountLoanInfo: constructAccountLoanTable(); m_columns << ctType << ctName; break; } // Sort the data to match the report definition m_subtotal << ctValue; switch (m_config.rowType()) { case eMyMoney::Report::RowType::Schedule: m_group << ctType; m_subtotal << ctValue; break; case eMyMoney::Report::RowType::AccountInfo: case eMyMoney::Report::RowType::AccountLoanInfo: m_group << ctTopCategory << ctInstitution; m_subtotal << ctCurrentBalance; break; default: throw MYMONEYEXCEPTION_CSTRING("ObjectInfoTable::ObjectInfoTable(): unhandled row type"); } QVector sort = QVector::fromList(m_group) << QVector::fromList(m_columns) << ctID << ctRank; switch (m_config.rowType()) { case eMyMoney::Report::RowType::Schedule: if (m_config.detailLevel() == eMyMoney::Report::DetailLevel::All) { m_columns << ctPayee << ctPaymentType << ctOccurrence << ctNextDueDate << ctCategory << ctValue; } else { m_columns << ctPayee << ctPaymentType << ctOccurrence << ctNextDueDate << ctValue; } break; case eMyMoney::Report::RowType::AccountInfo: m_columns << ctNumber << ctDescription << ctOpeningDate << ctCurrencyName << ctBalanceWarning << ctCreditWarning << ctMaxCreditLimit << ctTax << ctFavorite; break; case eMyMoney::Report::RowType::AccountLoanInfo: m_columns << ctNumber << ctDescription << ctOpeningDate << ctCurrencyName << ctPayee << ctLoanAmount << ctInterestRate << ctNextInterestChange << ctPeriodicPayment << ctFinalPayment << ctFavorite; break; default: m_columns.clear(); } TableRow::setSortCriteria(sort); qSort(m_rows); } void ObjectInfoTable::constructScheduleTable() { MyMoneyFile* file = MyMoneyFile::instance(); QList schedules; schedules = file->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, m_config.fromDate(), m_config.toDate(), false); 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[ctRank] = QLatin1Char('1'); //schedule data scheduleRow[ctID] = schedule.id(); scheduleRow[ctName] = schedule.name(); scheduleRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate); scheduleRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); scheduleRow[ctOccurrence] = i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1()); 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[ctValue] = (split.value() * xr).toString(); MyMoneyPayee payee = file->payee(split.payeeId()); scheduleRow[ctPayee] = payee.name(); m_rows += scheduleRow; //the text matches the main split bool transaction_text = m_config.match(split); if (m_config.detailLevel() == eMyMoney::Report::DetailLevel::All) { //get the information for all splits QList splits = transaction.splits(); QList::const_iterator split_it = splits.constBegin(); for (; split_it != splits.constEnd(); ++split_it) { + if ((*split_it).id() == split.id()) { + continue; + } TableRow splitRow; ReportAccount splitAcc((*split_it).accountId()); splitRow[ctRank] = QLatin1Char('2'); splitRow[ctID] = schedule.id(); splitRow[ctName] = schedule.name(); splitRow[ctPayee] = payee.name(); splitRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type()); splitRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate); if ((*split_it).value() == MyMoneyMoney::autoCalc) { splitRow[ctSplit] = MyMoneyMoney::autoCalc.toString(); } else if (! splitAcc.isIncomeExpense()) { splitRow[ctSplit] = (*split_it).value().toString(); } else { splitRow[ctSplit] = (- (*split_it).value()).toString(); } //if it is an assett account, mark it as a transfer if (! splitAcc.isIncomeExpense()) { splitRow[ctCategory] = ((* split_it).value().isNegative()) ? i18n("Transfer from %1" , splitAcc.fullName()) : i18n("Transfer to %1" , splitAcc.fullName()); } else { 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; + if (m_config.match((*split_it)) || transaction_text) { + // only add separate rows when we have a split transaction + // otherwise, we simply copy the category to the + // already added row and go on + if (splits.count() > 2) { + m_rows += splitRow; + } else { + m_rows.last()[ctCategory] = splitRow [ctCategory]; + } + } } } } ++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() != eMyMoney::Account::Type::Stock && !account.isClosed()) { MyMoneyMoney value; accountRow[ctRank] = QLatin1Char('1'); accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup()); if (!account.institutionId().isEmpty()) { accountRow[ctInstitution] = (file->institution(account.institutionId())).name(); } else { accountRow[ctInstitution] = QStringLiteral("Accounts with no institution assigned"); } accountRow[ctType] = MyMoneyAccount::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[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() == eMyMoney::Account::Type::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[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[ctRank] = QLatin1Char('1'); accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup()); if (!account.institutionId().isEmpty()) { accountRow[ctInstitution] = (file->institution(account.institutionId())).name(); } else { accountRow[ctInstitution] = QStringLiteral("Accounts with no institution assigned"); } accountRow[ctType] = MyMoneyAccount::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[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[ctCurrentBalance] = value.toString(); m_rows += accountRow; } ++it_account; } } MyMoneyMoney ObjectInfoTable::investmentBalance(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyMoney value = file->balance(acc.id()); foreach (const auto sAccount, acc.accountList()) { auto stock = file->account(sAccount); 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; } }