1 /*
2 * Copyright 2004-2005 Ace Jones <acejones@users.sourceforge.net>
3 * Copyright 2008-2010 Alvaro Soliverez <asoliverez@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "objectinfotable.h"
20
21 // ----------------------------------------------------------------------------
22 // QT Includes
23
24 #include <QList>
25 #include <QDate>
26 #include <QDebug>
27
28 // ----------------------------------------------------------------------------
29 // KDE Includes
30
31 #include <KLocalizedString>
32
33 // ----------------------------------------------------------------------------
34 // Project Includes
35
36 #include "mymoneyfile.h"
37 #include "mymoneyinstitution.h"
38 #include "mymoneyaccount.h"
39 #include "mymoneyaccountloan.h"
40 #include "mymoneysecurity.h"
41 #include "mymoneyprice.h"
42 #include "mymoneypayee.h"
43 #include "mymoneymoney.h"
44 #include "mymoneysplit.h"
45 #include "mymoneytransaction.h"
46 #include "mymoneyreport.h"
47 #include "mymoneyschedule.h"
48 #include "mymoneyexception.h"
49 #include "kmymoneyutils.h"
50 #include "reportaccount.h"
51 #include "mymoneyenums.h"
52
53 namespace reports
54 {
55
56 // ****************************************************************************
57 //
58 // ObjectInfoTable implementation
59 //
60 // ****************************************************************************
61
62 /**
63 * TODO
64 *
65 * - Collapse 2- & 3- groups when they are identical
66 * - Way more test cases (especially splits & transfers)
67 * - Option to collapse splits
68 * - Option to exclude transfers
69 *
70 */
71
ObjectInfoTable(const MyMoneyReport & _report)72 ObjectInfoTable::ObjectInfoTable(const MyMoneyReport& _report): ListTable(_report)
73 {
74 // separated into its own method to allow debugging (setting breakpoints
75 // directly in ctors somehow does not work for me (ipwizard))
76 // TODO: remove the init() method and move the code back to the ctor
77 init();
78 }
79
init()80 void ObjectInfoTable::init()
81 {
82 m_columns.clear();
83 m_group.clear();
84 m_subtotal.clear();
85 switch (m_config.rowType()) {
86 case eMyMoney::Report::RowType::Schedule:
87 constructScheduleTable();
88 m_columns << ctNextDueDate << ctName;
89 break;
90
91 default:
92 qDebug() << "ObjectInfoTable::ObjectInfoTable(): unhandled row type" << static_cast<uint>(m_config.rowType());
93 qDebug() << "turned into eMyMoney::Report::RowType::AccountInfo";
94 m_config.setRowType(eMyMoney::Report::RowType::AccountInfo);
95 // intentional fall through
96 case eMyMoney::Report::RowType::AccountInfo:
97 constructAccountTable();
98 m_columns << ctType << ctName;
99 break;
100
101 case eMyMoney::Report::RowType::AccountLoanInfo:
102 constructAccountLoanTable();
103 m_columns << ctType << ctName;
104 break;
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 m_group << ctType;
113 m_subtotal << ctValue;
114 break;
115 case eMyMoney::Report::RowType::AccountInfo:
116 case eMyMoney::Report::RowType::AccountLoanInfo:
117 m_group << ctTopCategory << ctInstitution;
118 m_subtotal << ctCurrentBalance;
119 break;
120 default:
121 throw MYMONEYEXCEPTION_CSTRING("ObjectInfoTable::ObjectInfoTable(): unhandled row type");
122 }
123
124 QVector<cellTypeE> sort = QVector<cellTypeE>::fromList(m_group) << QVector<cellTypeE>::fromList(m_columns) << ctID << ctRank;
125
126 switch (m_config.rowType()) {
127 case eMyMoney::Report::RowType::Schedule:
128 if (m_config.detailLevel() == eMyMoney::Report::DetailLevel::All) {
129 m_columns << ctPayee << ctPaymentType << ctOccurrence
130 << ctNextDueDate << ctCategory << ctValue;
131 } else {
132 m_columns << ctPayee << ctPaymentType << ctOccurrence
133 << ctNextDueDate << ctValue;
134 }
135 break;
136 case eMyMoney::Report::RowType::AccountInfo:
137 m_columns << ctNumber << ctDescription
138 << ctOpeningDate << ctCurrencyName << ctBalanceWarning
139 << ctCreditWarning << ctMaxCreditLimit
140 << ctTax << ctFavorite;
141 break;
142 case eMyMoney::Report::RowType::AccountLoanInfo:
143 m_columns << ctNumber << ctDescription
144 << ctOpeningDate << ctCurrencyName << ctPayee
145 << ctLoanAmount << ctInterestRate << ctNextInterestChange
146 << ctPeriodicPayment << ctFinalPayment << ctFavorite;
147 break;
148 default:
149 m_columns.clear();
150 }
151
152 TableRow::setSortCriteria(sort);
153 qSort(m_rows);
154 }
155
constructScheduleTable()156 void ObjectInfoTable::constructScheduleTable()
157 {
158 MyMoneyFile* file = MyMoneyFile::instance();
159
160 QList<MyMoneySchedule> schedules;
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);
163
164 QList<MyMoneySchedule>::const_iterator it_schedule = schedules.constBegin();
165 while (it_schedule != schedules.constEnd()) {
166 MyMoneySchedule schedule = *it_schedule;
167
168 ReportAccount account(schedule.account());
169
170 if (m_config.includes(account)) {
171 //get fraction for account
172 int fraction = account.fraction();
173
174 //use base currency fraction if not initialized
175 if (fraction == -1)
176 fraction = MyMoneyFile::instance()->baseCurrency().smallestAccountFraction();
177
178 TableRow scheduleRow;
179
180 //convert to base currency if needed
181 MyMoneyMoney xr = MyMoneyMoney::ONE;
182 if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
183 xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
184 }
185
186 // help for sort and render functions
187 scheduleRow[ctRank] = QLatin1Char('1');
188
189 //schedule data
190 scheduleRow[ctID] = schedule.id();
191 scheduleRow[ctName] = schedule.name();
192 scheduleRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate);
193 scheduleRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type());
194 scheduleRow[ctOccurrence] = i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1());
195 scheduleRow[ctPaymentType] = KMyMoneyUtils::paymentMethodToString(schedule.paymentType());
196
197 //scheduleRow["category"] = account.name();
198
199 //to get the payee we must look into the splits of the transaction
200 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();
205 m_rows += scheduleRow;
206
207 //the text matches the main split
208 bool transaction_text = m_config.match(split);
209
210 if (m_config.detailLevel() == eMyMoney::Report::DetailLevel::All) {
211 //get the information for all splits
212 QList<MyMoneySplit> splits = transaction.splits();
213 QList<MyMoneySplit>::const_iterator split_it = splits.constBegin();
214 for (; split_it != splits.constEnd(); ++split_it) {
215 if ((*split_it).id() == split.id()) {
216 continue;
217 }
218 TableRow splitRow;
219 ReportAccount splitAcc((*split_it).accountId());
220
221 splitRow[ctRank] = QLatin1Char('2');
222 splitRow[ctID] = schedule.id();
223 splitRow[ctName] = schedule.name();
224 splitRow[ctPayee] = payee.name();
225 splitRow[ctType] = KMyMoneyUtils::scheduleTypeToString(schedule.type());
226 splitRow[ctNextDueDate] = schedule.nextDueDate().toString(Qt::ISODate);
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();
232 } else {
233 splitRow[ctSplit] = (- (*split_it).value()).toString();
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 ? i18n("Transfer from %1" , splitAcc.fullName())
240 : i18n("Transfer to %1" , splitAcc.fullName());
241 } else {
242 splitRow [ctCategory] = splitAcc.fullName();
243 }
244
245 //add the split only if it matches the text or it matches the main split
246 if (m_config.match((*split_it)) || transaction_text) {
247 // only add separate rows when we have a split transaction
248 // otherwise, we simply copy the category to the
249 // already added row and go on
250 if (splits.count() > 2) {
251 m_rows += splitRow;
252 } else {
253 m_rows.last()[ctCategory] = splitRow [ctCategory];
254 }
255 }
256 }
257 }
258 }
259 ++it_schedule;
260 }
261 }
262
constructAccountTable()263 void ObjectInfoTable::constructAccountTable()
264 {
265 MyMoneyFile* file = MyMoneyFile::instance();
266
267 //make sure we have all subaccounts of investment accounts
268 includeInvestmentSubAccounts();
269
270 QList<MyMoneyAccount> accounts;
271 file->accountList(accounts);
272 QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
273 while (it_account != accounts.constEnd()) {
274 TableRow accountRow;
275 ReportAccount account(*it_account);
276
277 if (m_config.includes(account)
278 && account.accountType() != eMyMoney::Account::Type::Stock
279 && !account.isClosed()) {
280 MyMoneyMoney value;
281 accountRow[ctRank] = QLatin1Char('1');
282 accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup());
283 if (!account.institutionId().isEmpty()) {
284 accountRow[ctInstitution] = (file->institution(account.institutionId())).name();
285 } else {
286 accountRow[ctInstitution] = QStringLiteral("Accounts with no institution assigned");
287 }
288 accountRow[ctType] = MyMoneyAccount::accountTypeToString(account.accountType());
289 accountRow[ctName] = account.name();
290 accountRow[ctNumber] = account.number();
291 accountRow[ctDescription] = account.description();
292 accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate);
293 //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
294 accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name();
295 accountRow[ctBalanceWarning] = account.value("minBalanceEarly");
296 accountRow[ctMaxBalanceLimit] = account.value("minBalanceAbsolute");
297 accountRow[ctCreditWarning] = account.value("maxCreditEarly");
298 accountRow[ctMaxCreditLimit] = account.value("maxCreditAbsolute");
299 accountRow[ctTax] = account.value("Tax") == QLatin1String("Yes") ? i18nc("Is this a tax account?", "Yes") : QString();
300 accountRow[ctOpeningBalance] = account.value("OpeningBalanceAccount") == QLatin1String("Yes") ? i18nc("Is this an opening balance account?", "Yes") : QString();
301 accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString();
302
303 //investment accounts show the balances of all its subaccounts
304 if (account.accountType() == eMyMoney::Account::Type::Investment) {
305 value = investmentBalance(account);
306 } else {
307 value = file->balance(account.id());
308 }
309
310 //convert to base currency if needed
311 if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
312 MyMoneyMoney xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
313 value = value * xr;
314 }
315 accountRow[ctCurrentBalance] = value.toString();
316
317 m_rows += accountRow;
318 }
319 ++it_account;
320 }
321 }
322
constructAccountLoanTable()323 void ObjectInfoTable::constructAccountLoanTable()
324 {
325 MyMoneyFile* file = MyMoneyFile::instance();
326
327 QList<MyMoneyAccount> accounts;
328 file->accountList(accounts);
329 QList<MyMoneyAccount>::const_iterator it_account = accounts.constBegin();
330 while (it_account != accounts.constEnd()) {
331 TableRow accountRow;
332 ReportAccount account(*it_account);
333 MyMoneyAccountLoan loan = *it_account;
334
335 if (m_config.includes(account) && account.isLoan() && !account.isClosed()) {
336 //convert to base currency if needed
337 MyMoneyMoney xr = MyMoneyMoney::ONE;
338 if (m_config.isConvertCurrency() && account.isForeignCurrency()) {
339 xr = account.baseCurrencyPrice(QDate::currentDate()).reduce();
340 }
341
342 accountRow[ctRank] = QLatin1Char('1');
343 accountRow[ctTopCategory] = MyMoneyAccount::accountTypeToString(account.accountGroup());
344 if (!account.institutionId().isEmpty()) {
345 accountRow[ctInstitution] = (file->institution(account.institutionId())).name();
346 } else {
347 accountRow[ctInstitution] = QStringLiteral("Accounts with no institution assigned");
348 }
349 accountRow[ctType] = MyMoneyAccount::accountTypeToString(account.accountType());
350 accountRow[ctName] = account.name();
351 accountRow[ctNumber] = account.number();
352 accountRow[ctDescription] = account.description();
353 accountRow[ctOpeningDate] = account.openingDate().toString(Qt::ISODate);
354 //accountRow["currency"] = (file->currency(account.currencyId())).tradingSymbol();
355 accountRow[ctCurrencyName] = (file->currency(account.currencyId())).name();
356 accountRow[ctPayee] = file->payee(loan.payee()).name();
357 accountRow[ctLoanAmount] = (loan.loanAmount() * xr).toString();
358 accountRow[ctInterestRate] = (loan.interestRate(QDate::currentDate()) / MyMoneyMoney(100, 1) * xr).toString();
359 accountRow[ctNextInterestChange] = loan.nextInterestChange().toString(Qt::ISODate);
360 accountRow[ctPeriodicPayment] = (loan.periodicPayment() * xr).toString();
361 accountRow[ctFinalPayment] = (loan.finalPayment() * xr).toString();
362 accountRow[ctFavorite] = account.value("PreferredAccount") == QLatin1String("Yes") ? i18nc("Is this a favorite account?", "Yes") : QString();
363
364 MyMoneyMoney value = file->balance(account.id());
365 value = value * xr;
366 accountRow[ctCurrentBalance] = value.toString();
367 m_rows += accountRow;
368 }
369 ++it_account;
370 }
371 }
372
investmentBalance(const MyMoneyAccount & acc)373 MyMoneyMoney ObjectInfoTable::investmentBalance(const MyMoneyAccount& acc)
374 {
375 MyMoneyFile* file = MyMoneyFile::instance();
376 MyMoneyMoney value = file->balance(acc.id());
377
378 foreach (const auto sAccount, acc.accountList()) {
379 auto stock = file->account(sAccount);
380 try {
381 MyMoneyMoney val;
382 MyMoneyMoney balance = file->balance(stock.id());
383 MyMoneySecurity security = file->security(stock.currencyId());
384 const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency());
385 val = balance * price.rate(security.tradingCurrency());
386 // adjust value of security to the currency of the account
387 MyMoneySecurity accountCurrency = file->currency(acc.currencyId());
388 val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id());
389 val = val.convert(acc.fraction());
390 value += val;
391 } catch (const MyMoneyException &e) {
392 qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what())));
393 }
394 }
395 return value;
396 }
397
398 }
399