objectinfotable.cpp 15.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * Copyright 2004-2005  Ace Jones <acejones@users.sourceforge.net>
 * Copyright 2008-2010  Alvaro Soliverez <asoliverez@gmail.com>
 *
 * 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 <http://www.gnu.org/licenses/>.
 */
Alvaro Soliverez's avatar
Alvaro Soliverez committed
18

Cristian Oneț's avatar
Cristian Oneț committed
19
20
#include "objectinfotable.h"

Alvaro Soliverez's avatar
Alvaro Soliverez committed
21
22
// ----------------------------------------------------------------------------
// QT Includes
23

Nicolas Lécureuil's avatar
Nicolas Lécureuil committed
24
#include <QList>
25
#include <QDate>
26
#include <QDebug>
Alvaro Soliverez's avatar
Alvaro Soliverez committed
27
28
29
30

// ----------------------------------------------------------------------------
// KDE Includes

31
#include <KLocalizedString>
Alvaro Soliverez's avatar
Alvaro Soliverez committed
32
33
34

// ----------------------------------------------------------------------------
// Project Includes
35

Laurent Montel's avatar
Laurent Montel committed
36
#include "mymoneyfile.h"
37
#include "mymoneyinstitution.h"
38
39
40
#include "mymoneyaccount.h"
#include "mymoneyaccountloan.h"
#include "mymoneysecurity.h"
41
42
#include "mymoneyprice.h"
#include "mymoneypayee.h"
43
#include "mymoneymoney.h"
44
45
#include "mymoneysplit.h"
#include "mymoneytransaction.h"
Laurent Montel's avatar
Laurent Montel committed
46
#include "mymoneyreport.h"
47
#include "mymoneyschedule.h"
Laurent Montel's avatar
Laurent Montel committed
48
49
#include "mymoneyexception.h"
#include "kmymoneyutils.h"
Alvaro Soliverez's avatar
Alvaro Soliverez committed
50
#include "reportaccount.h"
51
#include "mymoneyenums.h"
Alvaro Soliverez's avatar
Alvaro Soliverez committed
52

53
54
namespace reports
{
Alvaro Soliverez's avatar
Alvaro Soliverez committed
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

// ****************************************************************************
//
// 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)
{
Alvaro Soliverez's avatar
Alvaro Soliverez committed
74
  // separated into its own method to allow debugging (setting breakpoints
Alvaro Soliverez's avatar
Alvaro Soliverez committed
75
76
77
78
79
  // directly in ctors somehow does not work for me (ipwizard))
  // TODO: remove the init() method and move the code back to the ctor
  init();
}

80
void ObjectInfoTable::init()
Alvaro Soliverez's avatar
Alvaro Soliverez committed
81
{
82
83
84
  m_columns.clear();
  m_group.clear();
  m_subtotal.clear();
85
  switch (m_config.rowType()) {
86
    case eMyMoney::Report::RowType::Schedule:
Ian Neal's avatar
Ian Neal committed
87
      constructScheduleTable();
88
      m_columns << ctNextDueDate << ctName;
Ian Neal's avatar
Ian Neal committed
89
      break;
90
91
92
93
94
95

    default:
      qDebug() << "ObjectInfoTable::ObjectInfoTable(): unhandled row type" << static_cast<uint>(m_config.rowType());
      qDebug() << "turned into eMyMoney::Report::RowType::AccountInfo";
      m_config.setRowType(eMyMoney::Report::RowType::AccountInfo);
      // intentional fall through
96
    case eMyMoney::Report::RowType::AccountInfo:
Ian Neal's avatar
Ian Neal committed
97
      constructAccountTable();
Thomas Baumgart's avatar
Thomas Baumgart committed
98
      m_columns << ctType << ctName;
Ian Neal's avatar
Ian Neal committed
99
      break;
100

101
    case eMyMoney::Report::RowType::AccountLoanInfo:
Ian Neal's avatar
Ian Neal committed
102
      constructAccountLoanTable();
Thomas Baumgart's avatar
Thomas Baumgart committed
103
      m_columns << ctType << ctName;
Ian Neal's avatar
Ian Neal committed
104
      break;
Alvaro Soliverez's avatar
Alvaro Soliverez committed
105
106
107
  }

  // Sort the data to match the report definition
108
  m_subtotal << ctValue;
109
110

  switch (m_config.rowType()) {
111
    case eMyMoney::Report::RowType::Schedule:
112
113
      m_group << ctType;
      m_subtotal << ctValue;
Ian Neal's avatar
Ian Neal committed
114
      break;
115
116
    case eMyMoney::Report::RowType::AccountInfo:
    case eMyMoney::Report::RowType::AccountLoanInfo:
117
118
      m_group << ctTopCategory << ctInstitution;
      m_subtotal << ctCurrentBalance;
Ian Neal's avatar
Ian Neal committed
119
120
      break;
    default:
121
      throw MYMONEYEXCEPTION_CSTRING("ObjectInfoTable::ObjectInfoTable(): unhandled row type");
Alvaro Soliverez's avatar
Alvaro Soliverez committed
122
123
  }

124
  QVector<cellTypeE> sort = QVector<cellTypeE>::fromList(m_group) << QVector<cellTypeE>::fromList(m_columns) << ctID << ctRank;
Alvaro Soliverez's avatar
Alvaro Soliverez committed
125

126
  switch (m_config.rowType()) {
127
128
    case eMyMoney::Report::RowType::Schedule:
      if (m_config.detailLevel() == eMyMoney::Report::DetailLevel::All) {
Thomas Baumgart's avatar
Thomas Baumgart committed
129
        m_columns << ctPayee << ctPaymentType << ctOccurrence
130
                  << ctNextDueDate << ctCategory << ctValue;
Ian Neal's avatar
Ian Neal committed
131
      } else {
Thomas Baumgart's avatar
Thomas Baumgart committed
132
        m_columns << ctPayee << ctPaymentType << ctOccurrence
133
                  << ctNextDueDate << ctValue;
Ian Neal's avatar
Ian Neal committed
134
135
      }
      break;
136
    case eMyMoney::Report::RowType::AccountInfo:
Thomas Baumgart's avatar
Thomas Baumgart committed
137
      m_columns << ctNumber << ctDescription
138
139
140
                << ctOpeningDate << ctCurrencyName << ctBalanceWarning
                << ctCreditWarning << ctMaxCreditLimit
                << ctTax << ctFavorite;
Ian Neal's avatar
Ian Neal committed
141
      break;
142
    case eMyMoney::Report::RowType::AccountLoanInfo:
Thomas Baumgart's avatar
Thomas Baumgart committed
143
      m_columns << ctNumber << ctDescription
144
145
146
                << ctOpeningDate << ctCurrencyName << ctPayee
                << ctLoanAmount << ctInterestRate << ctNextInterestChange
                << ctPeriodicPayment << ctFinalPayment << ctFavorite;
Ian Neal's avatar
Ian Neal committed
147
148
      break;
    default:
149
      m_columns.clear();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
150
151
  }

152
153
  TableRow::setSortCriteria(sort);
  qSort(m_rows);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
154
155
}

156
void ObjectInfoTable::constructScheduleTable()
Alvaro Soliverez's avatar
Alvaro Soliverez committed
157
158
159
{
  MyMoneyFile* file = MyMoneyFile::instance();

160
  QList<MyMoneySchedule> schedules;
Alvaro Soliverez's avatar
Alvaro Soliverez committed
161

162
  schedules = file->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, m_config.fromDate(), m_config.toDate(), false);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
163

Laurent Montel's avatar
Laurent Montel committed
164
  QList<MyMoneySchedule>::const_iterator it_schedule = schedules.constBegin();
165
  while (it_schedule != schedules.constEnd()) {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
166
167
    MyMoneySchedule schedule = *it_schedule;

168
    ReportAccount account(schedule.account());
Alvaro Soliverez's avatar
Alvaro Soliverez committed
169

170
    if (m_config.includes(account))  {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
171
172
173
174
      //get fraction for account
      int fraction = account.fraction();

      //use base currency fraction if not initialized
175
      if (fraction == -1)
Alvaro Soliverez's avatar
Alvaro Soliverez committed
176
177
178
179
180
        fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction();

      TableRow scheduleRow;

      //convert to base currency if needed
181
      MyMoneyMoney xr = MyMoneyMoney::ONE;
Alvaro Soliverez's avatar
Alvaro Soliverez committed
182
183
184
185
186
      if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
        xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
      }

      // help for sort and render functions
Thomas Baumgart's avatar
Thomas Baumgart committed
187
      scheduleRow[ctRank] = QLatin1Char('1');
Alvaro Soliverez's avatar
Alvaro Soliverez committed
188
189

      //schedule data
190
191
192
193
      scheduleRow[ctID] = schedule.id();
      scheduleRow[ctName] = schedule.name();
      scheduleRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate);
      scheduleRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type());
Łukasz Wojniłowicz's avatar
Łukasz Wojniłowicz committed
194
      scheduleRow[ctOccurrence] = i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1());
195
      scheduleRow[ctPaymentType] = KMyMoneyUtils::paymentMethodToString(schedule.paymentType());
Alvaro Soliverez's avatar
Alvaro Soliverez committed
196
197
198
199
200

      //scheduleRow["category"] = account.name();

      //to get the payee we must look into the splits of the transaction
      MyMoneyTransaction transaction = schedule.transaction();
201
      MyMoneySplit split = transaction.splitByAccount(account.id(), true);
202
      scheduleRow[ctValue] = (split.value() * xr).toString();
203
      MyMoneyPayee payee = file->payee(split.payeeId());
204
      scheduleRow[ctPayee] = payee.name();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
205
206
207
      m_rows += scheduleRow;

      //the text matches the main split
208
      bool transaction_text = m_config.match(split);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
209

210
      if (m_config.detailLevel() == eMyMoney::Report::DetailLevel::All) {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
211
        //get the information for all splits
212
        QList<MyMoneySplit> splits = transaction.splits();
Laurent Montel's avatar
Laurent Montel committed
213
        QList<MyMoneySplit>::const_iterator split_it = splits.constBegin();
Thomas Baumgart's avatar
Thomas Baumgart committed
214
        for (; split_it != splits.constEnd(); ++split_it) {
215
216
217
          if ((*split_it).id() == split.id()) {
            continue;
          }
Alvaro Soliverez's avatar
Alvaro Soliverez committed
218
          TableRow splitRow;
219
          ReportAccount splitAcc((*split_it).accountId());
Alvaro Soliverez's avatar
Alvaro Soliverez committed
220

Thomas Baumgart's avatar
Thomas Baumgart committed
221
          splitRow[ctRank] = QLatin1Char('2');
222
223
          splitRow[ctID] = schedule.id();
          splitRow[ctName] = schedule.name();
Thomas Baumgart's avatar
Thomas Baumgart committed
224
          splitRow[ctPayee] = payee.name();
225
226
          splitRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type());
          splitRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
227

228
          if ((*split_it).value() == MyMoneyMoney::autoCalc) {
229
            splitRow[ctSplit] = MyMoneyMoney::autoCalc.toString();
230
          } else if (! splitAcc.isIncomeExpense()) {
231
            splitRow[ctSplit] = (*split_it).value().toString();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
232
          } else {
233
            splitRow[ctSplit] = (- (*split_it).value()).toString();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
234
235
236
          }

          //if it is an assett account, mark it as a transfer
237
          if (! splitAcc.isIncomeExpense()) {
238
            splitRow[ctCategory] = ((* split_it).value().isNegative())
239
240
                                   ? i18n("Transfer from %1" , splitAcc.fullName())
                                   : i18n("Transfer to %1" , splitAcc.fullName());
Alvaro Soliverez's avatar
Alvaro Soliverez committed
241
          } else {
242
            splitRow [ctCategory] = splitAcc.fullName();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
243
244
245
          }

          //add the split only if it matches the text or it matches the main split
246
247
248
249
250
251
252
253
254
255
          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];
            }
          }
Alvaro Soliverez's avatar
Alvaro Soliverez committed
256
257
258
259
260
261
262
        }
      }
    }
    ++it_schedule;
  }
}

263
void ObjectInfoTable::constructAccountTable()
Alvaro Soliverez's avatar
Alvaro Soliverez committed
264
265
266
{
  MyMoneyFile* file = MyMoneyFile::instance();

267
268
269
  //make sure we have all subaccounts of investment accounts
  includeInvestmentSubAccounts();

270
  QList<MyMoneyAccount> accounts;
Alvaro Soliverez's avatar
Alvaro Soliverez committed
271
  file->accountList(accounts);
Laurent Montel's avatar
Laurent Montel committed
272
  QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
273
  while (it_account != accounts.constEnd()) {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
274
    TableRow accountRow;
275
    ReportAccount account(*it_account);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
276

277
    if (m_config.includes(account)
278
        && account.accountType() != eMyMoney::Account::Type::Stock
279
        && !account.isClosed()) {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
280
      MyMoneyMoney value;
Thomas Baumgart's avatar
Thomas Baumgart committed
281
      accountRow[ctRank] = QLatin1Char('1');
282
      accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup());
Thomas Baumgart's avatar
Thomas Baumgart committed
283
284
285
286
287
      if (!account.institutionId().isEmpty()) {
        accountRow[ctInstitution] = (file->institution(account.institutionId())).name();
      } else {
        accountRow[ctInstitution] = QStringLiteral("Accounts with no institution assigned");
      }
288
      accountRow[ctType] = MyMoneyAccount::accountTypeToString(account.accountType());
289
290
291
292
      accountRow[ctName] = account.name();
      accountRow[ctNumber] = account.number();
      accountRow[ctDescription] = account.description();
      accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
293
      //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
294
295
296
297
298
299
300
301
      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();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
302
303

      //investment accounts show the balances of all its subaccounts
304
      if (account.accountType() == eMyMoney::Account::Type::Investment) {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
305
306
307
308
309
310
311
312
313
314
        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;
      }
315
      accountRow[ctCurrentBalance] = value.toString();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
316
317
318
319
320
321
322

      m_rows += accountRow;
    }
    ++it_account;
  }
}

323
void ObjectInfoTable::constructAccountLoanTable()
Alvaro Soliverez's avatar
Alvaro Soliverez committed
324
325
326
{
  MyMoneyFile* file = MyMoneyFile::instance();

327
  QList<MyMoneyAccount> accounts;
Alvaro Soliverez's avatar
Alvaro Soliverez committed
328
  file->accountList(accounts);
Laurent Montel's avatar
Laurent Montel committed
329
  QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
330
  while (it_account != accounts.constEnd()) {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
331
    TableRow accountRow;
332
    ReportAccount account(*it_account);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
333
334
    MyMoneyAccountLoan loan = *it_account;

Cristian Oneț's avatar
Cristian Oneț committed
335
    if (m_config.includes(account) && account.isLoan() && !account.isClosed()) {
Alvaro Soliverez's avatar
Alvaro Soliverez committed
336
      //convert to base currency if needed
337
      MyMoneyMoney xr = MyMoneyMoney::ONE;
Alvaro Soliverez's avatar
Alvaro Soliverez committed
338
      if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
339
        xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
340
341
      }

Thomas Baumgart's avatar
Thomas Baumgart committed
342
      accountRow[ctRank] = QLatin1Char('1');
343
      accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup());
Thomas Baumgart's avatar
Thomas Baumgart committed
344
345
346
347
348
      if (!account.institutionId().isEmpty()) {
        accountRow[ctInstitution] = (file->institution(account.institutionId())).name();
      } else {
        accountRow[ctInstitution] = QStringLiteral("Accounts with no institution assigned");
      }
349
      accountRow[ctType] = MyMoneyAccount::accountTypeToString(account.accountType());
350
351
352
353
      accountRow[ctName] = account.name();
      accountRow[ctNumber] = account.number();
      accountRow[ctDescription] = account.description();
      accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
354
      //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
355
356
357
358
359
360
361
362
      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();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
363
364
365

      MyMoneyMoney value = file->balance(account.id());
      value = value * xr;
366
      accountRow[ctCurrentBalance] = value.toString();
Alvaro Soliverez's avatar
Alvaro Soliverez committed
367
368
369
370
371
372
373
374
375
      m_rows += accountRow;
    }
    ++it_account;
  }
}

MyMoneyMoney ObjectInfoTable::investmentBalance(const MyMoneyAccount& acc)
{
  MyMoneyFile* file = MyMoneyFile::instance();
376
  MyMoneyMoney value = file->balance(acc.id());
Alvaro Soliverez's avatar
Alvaro Soliverez committed
377

378
379
  foreach (const auto sAccount, acc.accountList()) {
    auto stock = file->account(sAccount);
Alvaro Soliverez's avatar
Alvaro Soliverez committed
380
381
382
383
    try {
      MyMoneyMoney val;
      MyMoneyMoney balance = file->balance(stock.id());
      MyMoneySecurity security = file->security(stock.currencyId());
384
      const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency());
Alvaro Soliverez's avatar
Alvaro Soliverez committed
385
386
387
388
389
390
      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;
391
392
    } catch (const MyMoneyException &e) {
      qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what())));
Alvaro Soliverez's avatar
Alvaro Soliverez committed
393
394
395
396
397
398
    }
  }
  return value;
}

}