Changeset View
Changeset View
Standalone View
Standalone View
kmymoney/reports/querytable.cpp
Show First 20 Lines • Show All 332 Lines • ▼ Show 20 Line(s) | 331 | case MyMoneyReport::eAccountType: | |||
---|---|---|---|---|---|
333 | break; | 333 | break; | ||
334 | case MyMoneyReport::eInstitution: | 334 | case MyMoneyReport::eInstitution: | ||
335 | m_group = "institution,topaccount"; | 335 | m_group = "institution,topaccount"; | ||
336 | break; | 336 | break; | ||
337 | default: | 337 | default: | ||
338 | throw MYMONEYEXCEPTION("QueryTable::QueryTable(): unhandled row type"); | 338 | throw MYMONEYEXCEPTION("QueryTable::QueryTable(): unhandled row type"); | ||
339 | } | 339 | } | ||
340 | 340 | | |||
341 | QString sort = m_group + ',' + m_columns + ",id,rank"; | 341 | QString sort = m_group + ",id,rank," + m_columns; | ||
342 | 342 | | |||
343 | switch (m_config.rowType()) { | 343 | switch (m_config.rowType()) { | ||
344 | case MyMoneyReport::eAccountByTopAccount: | 344 | case MyMoneyReport::eAccountByTopAccount: | ||
345 | case MyMoneyReport::eEquityType: | 345 | case MyMoneyReport::eEquityType: | ||
346 | case MyMoneyReport::eAccountType: | 346 | case MyMoneyReport::eAccountType: | ||
347 | case MyMoneyReport::eInstitution: | 347 | case MyMoneyReport::eInstitution: | ||
348 | m_columns = "account"; | 348 | m_columns = "account"; | ||
349 | break; | 349 | break; | ||
Show All 20 Lines | 369 | if (qc & MyMoneyReport::eQCmemo) | |||
370 | m_columns += ",memo"; | 370 | m_columns += ",memo"; | ||
371 | if (qc & MyMoneyReport::eQCaction) | 371 | if (qc & MyMoneyReport::eQCaction) | ||
372 | m_columns += ",action"; | 372 | m_columns += ",action"; | ||
373 | if (qc & MyMoneyReport::eQCshares) | 373 | if (qc & MyMoneyReport::eQCshares) | ||
374 | m_columns += ",shares"; | 374 | m_columns += ",shares"; | ||
375 | if (qc & MyMoneyReport::eQCprice) | 375 | if (qc & MyMoneyReport::eQCprice) | ||
376 | m_columns += ",price"; | 376 | m_columns += ",price"; | ||
377 | if (qc & MyMoneyReport::eQCperformance) { | 377 | if (qc & MyMoneyReport::eQCperformance) { | ||
378 | m_columns += ",startingbal,buys,sells,reinvestincome,cashincome,return,returninvestment"; | 378 | m_columns += ",startingbal,buys,sells,reinvestincome,cashincome,return,returninvestment,endingbal"; | ||
379 | m_subtotal = "endingbal"; | 379 | m_subtotal = "startingbal,buys,sells,reinvestincome,cashincome,return,returninvestment,endingbal"; | ||
380 | } | 380 | } | ||
381 | if (qc & MyMoneyReport::eQCcapitalgain) { | 381 | if (qc & MyMoneyReport::eQCcapitalgain) { | ||
382 | m_columns += ",buys,sells"; | 382 | m_columns += ",buys,sells,capitalgain"; | ||
383 | m_subtotal = "capitalgain"; | 383 | m_subtotal = "buys,sells,capitalgain"; | ||
384 | } | 384 | } | ||
385 | if (qc & MyMoneyReport::eQCloan) { | 385 | if (qc & MyMoneyReport::eQCloan) { | ||
386 | m_columns += ",payment,interest,fees"; | 386 | m_columns += ",payment,interest,fees"; | ||
387 | m_postcolumns = "balance"; | 387 | m_postcolumns = "balance"; | ||
388 | } | 388 | } | ||
389 | if (qc & MyMoneyReport::eQCbalance) | 389 | if (qc & MyMoneyReport::eQCbalance) | ||
390 | m_postcolumns = "balance"; | 390 | m_postcolumns = "balance"; | ||
391 | 391 | | |||
392 | TableRow::setSortCriteria(sort); | 392 | TableRow::setSortCriteria(sort); | ||
393 | | ||||
394 | qSort(m_rows); | 393 | qSort(m_rows); | ||
394 | | ||||
395 | constructTotalRows(); // adds total rows to m_rows | ||||
396 | } | ||||
397 | | ||||
398 | void QueryTable::constructTotalRows() | ||||
399 | { | ||||
400 | if (m_rows.isEmpty()) | ||||
401 | return; | ||||
402 | | ||||
403 | // qSort places grand total at last position, because it doesn't belong to any group | ||||
404 | if (m_rows.at(0)["rank"] == "4") // it should be unlikely that total row is at the top of rows, so... | ||||
405 | m_rows.move(0, m_rows.count() - 1); // ...move it at the bottom | ||||
406 | | ||||
407 | QStringList subtotals = m_subtotal.split(','); | ||||
408 | QStringList groups = m_group.split(','); | ||||
409 | QStringList columns = m_columns.split(','); | ||||
410 | if (!m_subtotal.isEmpty() && subtotals.count() == 1) | ||||
411 | columns += m_subtotal; | ||||
412 | QStringList postcolumns = m_postcolumns.split(','); | ||||
413 | if (!m_postcolumns.isEmpty()) | ||||
414 | columns += postcolumns; | ||||
415 | | ||||
416 | QList<QMap<QString, MyMoneyMoney>> totalGroups; | ||||
417 | QMap<QString, MyMoneyMoney> totalsValues; | ||||
418 | | ||||
419 | // initialize all total values under summed columns to be zero | ||||
420 | foreach (auto subtotal, subtotals) { | ||||
421 | totalsValues.insert(subtotal, MyMoneyMoney()); | ||||
422 | } | ||||
423 | | ||||
424 | // create total groups containing totals row for each group | ||||
425 | totalGroups.append(totalsValues); // prepend with extra group for grand total | ||||
426 | for (int j = 0; j < groups.count(); ++j) { | ||||
427 | totalGroups.append(totalsValues); | ||||
428 | } | ||||
429 | | ||||
430 | QList<TableRow> stashedTotalRows; | ||||
431 | QList<TableRow>::iterator current_row, next_row; | ||||
432 | for (current_row = m_rows.begin(); | ||||
433 | current_row != m_rows.end();) { | ||||
434 | | ||||
435 | next_row = std::next(current_row); | ||||
436 | | ||||
437 | // total rows are useless at summing so remove whole block of them at once | ||||
438 | while (next_row != m_rows.end() && (*next_row)["rank"] == "4") { | ||||
439 | stashedTotalRows.append((*next_row)); // ...but stash them just in case | ||||
440 | next_row = m_rows.erase(next_row); | ||||
441 | } | ||||
442 | | ||||
443 | bool lastRow = (next_row == m_rows.end()); | ||||
444 | | ||||
445 | // sum all subtotal values for lowest group | ||||
446 | foreach (auto subtotal, subtotals) { | ||||
447 | totalGroups.last()[subtotal] += MyMoneyMoney((*current_row)[subtotal]); | ||||
448 | } | ||||
449 | | ||||
450 | // iterate over groups from the lowest to the highest to find group change | ||||
451 | for (int i = groups.count() - 1; i >= 0 ; --i) { | ||||
452 | // if any of groups from next row changes (or next row is the last row), then it's time to put totals row | ||||
453 | if (lastRow || (*current_row)[groups.at(i)] != (*next_row)[groups.at(i)]) { | ||||
454 | TableRow totalsRow; | ||||
455 | // custom total values calculations | ||||
456 | foreach (auto subtotal, subtotals) { | ||||
457 | if (subtotal == "returninvestment") | ||||
458 | totalGroups[i + 1]["returninvestment"] = helperROI(totalGroups[i + 1]["buys"], totalGroups[i + 1]["sells"], | ||||
459 | totalGroups[i + 1]["startingbal"], totalGroups[i + 1]["endingbal"], | ||||
460 | totalGroups[i + 1]["cashincome"]); | ||||
461 | } | ||||
462 | | ||||
463 | // total values that aren't calculated here, but are taken untouched from external source, e.g. constructPerformanceRow | ||||
464 | if (!stashedTotalRows.isEmpty()) { | ||||
465 | foreach (auto subtotal, subtotals) { | ||||
466 | if (subtotal == "return") | ||||
467 | totalsRow["return"] = stashedTotalRows.first()["return"]; | ||||
468 | } | ||||
469 | stashedTotalRows.removeFirst(); | ||||
470 | } | ||||
471 | | ||||
472 | // sum all subtotal values for higher groups (excluding grand total) and reset lowest group values | ||||
473 | QMap<QString, MyMoneyMoney>::iterator upperGrp = totalGroups[i].begin(); | ||||
474 | QMap<QString, MyMoneyMoney>::iterator lowerGrp = totalGroups[i + 1].begin(); | ||||
475 | | ||||
476 | while(upperGrp != totalGroups[i].end()) { | ||||
477 | totalsRow[lowerGrp.key()] = lowerGrp.value().toString(); // fill totals row with subtotal values... | ||||
478 | (*upperGrp) += (*lowerGrp); | ||||
479 | (*lowerGrp) = MyMoneyMoney(); | ||||
480 | ++upperGrp; | ||||
481 | ++lowerGrp; | ||||
482 | } | ||||
483 | | ||||
484 | for (int j = 0; j < groups.count(); ++j) { | ||||
485 | totalsRow[groups.at(j)] = (*current_row)[groups.at(j)]; // ...and identification | ||||
486 | } | ||||
487 | | ||||
488 | totalsRow["rank"] = "4"; | ||||
489 | totalsRow["depth"] = QString::number(i); | ||||
490 | | ||||
491 | if (lastRow) | ||||
492 | m_rows.append(totalsRow); | ||||
493 | else { | ||||
494 | next_row = m_rows.insert(next_row, totalsRow); // current_row and next_row can diverge here by more than one | ||||
495 | ++next_row; | ||||
496 | } | ||||
497 | } | ||||
498 | } | ||||
499 | | ||||
500 | // code to put grand total row | ||||
501 | if (lastRow) { | ||||
502 | TableRow totalsRow; | ||||
503 | | ||||
504 | foreach (auto subtotal, subtotals) { | ||||
505 | if (subtotal == "returninvestment") | ||||
506 | totalGroups[0]["returninvestment"] = helperROI(totalGroups[0]["buys"], totalGroups[0]["sells"], | ||||
507 | totalGroups[0]["startingbal"], totalGroups[0]["endingbal"], | ||||
508 | totalGroups[0]["cashincome"]); | ||||
509 | } | ||||
510 | | ||||
511 | if (!stashedTotalRows.isEmpty()) { | ||||
512 | foreach (auto subtotal, subtotals) { | ||||
513 | if (subtotal == "return") | ||||
514 | totalsRow["return"] = stashedTotalRows.first()["return"]; | ||||
515 | } | ||||
516 | stashedTotalRows.removeFirst(); | ||||
517 | } | ||||
518 | | ||||
519 | QMap<QString, MyMoneyMoney>::const_iterator grandTotalGrp = totalGroups[0].begin(); | ||||
520 | while(grandTotalGrp != totalGroups[0].end()) { | ||||
521 | totalsRow[grandTotalGrp.key()] = grandTotalGrp.value().toString(); | ||||
522 | ++grandTotalGrp; | ||||
523 | } | ||||
524 | | ||||
525 | for (int j = 0; j < groups.count(); ++j) { | ||||
526 | totalsRow[groups.at(j)] = QString(); // no identification | ||||
527 | } | ||||
528 | | ||||
529 | totalsRow["rank"] = "4"; | ||||
530 | totalsRow["depth"] = ""; | ||||
531 | m_rows.append(totalsRow); | ||||
532 | break; // no use to loop further | ||||
533 | } | ||||
534 | current_row = next_row; // current_row makes here a leap forward by at least one | ||||
535 | } | ||||
395 | } | 536 | } | ||
396 | 537 | | |||
397 | void QueryTable::constructTransactionTable() | 538 | void QueryTable::constructTransactionTable() | ||
398 | { | 539 | { | ||
399 | MyMoneyFile* file = MyMoneyFile::instance(); | 540 | MyMoneyFile* file = MyMoneyFile::instance(); | ||
400 | 541 | | |||
401 | //make sure we have all subaccounts of investment accounts | 542 | //make sure we have all subaccounts of investment accounts | ||
402 | includeInvestmentSubAccounts(); | 543 | includeInvestmentSubAccounts(); | ||
▲ Show 20 Lines • Show All 275 Lines • ▼ Show 20 Line(s) | 718 | if (it_split == myBegin) { | |||
678 | // only include the configured accounts | 819 | // only include the configured accounts | ||
679 | if (include_me) { | 820 | if (include_me) { | ||
680 | 821 | | |||
681 | if (loan_special_case) { | 822 | if (loan_special_case) { | ||
682 | 823 | | |||
683 | // put the principal amount in the "value" column and convert to lowest fraction | 824 | // put the principal amount in the "value" column and convert to lowest fraction | ||
684 | qA["value"] = (-(*it_split).shares() * xr).convert(fraction).toString(); | 825 | qA["value"] = (-(*it_split).shares() * xr).convert(fraction).toString(); | ||
685 | 826 | | |||
686 | qA["rank"] = '0'; | 827 | qA["rank"] = '1'; | ||
687 | qA["split"] = ""; | 828 | qA["split"] = ""; | ||
688 | 829 | | |||
689 | } else { | 830 | } else { | ||
690 | if ((splits.count() > 2) && use_summary) { | 831 | if ((splits.count() > 2) && use_summary) { | ||
691 | 832 | | |||
692 | // add the "summarized" split transaction | 833 | // add the "summarized" split transaction | ||
693 | // this is the sub-total of the split detail | 834 | // this is the sub-total of the split detail | ||
694 | // convert to lowest fraction | 835 | // convert to lowest fraction | ||
695 | qA["rank"] = '0'; | 836 | qA["rank"] = '1'; | ||
696 | qA["category"] = i18n("[Split Transaction]"); | 837 | qA["category"] = i18n("[Split Transaction]"); | ||
697 | qA["topcategory"] = i18nc("Split transaction", "Split"); | 838 | qA["topcategory"] = i18nc("Split transaction", "Split"); | ||
698 | qA["categorytype"] = i18nc("Split transaction", "Split"); | 839 | qA["categorytype"] = i18nc("Split transaction", "Split"); | ||
699 | 840 | | |||
700 | m_rows += qA; | 841 | m_rows += qA; | ||
701 | } | 842 | } | ||
702 | } | 843 | } | ||
703 | } | 844 | } | ||
Show All 37 Lines | 848 | if (include_me) { | |||
741 | else { | 882 | else { | ||
742 | 883 | | |||
743 | //this is when the splits are going to be shown as children of the main split | 884 | //this is when the splits are going to be shown as children of the main split | ||
744 | if ((splits.count() > 2) && use_summary) { | 885 | if ((splits.count() > 2) && use_summary) { | ||
745 | qA["value"] = ""; | 886 | qA["value"] = ""; | ||
746 | 887 | | |||
747 | //convert to lowest fraction | 888 | //convert to lowest fraction | ||
748 | qA["split"] = (-(*it_split).shares() * xr).convert(fraction).toString(); | 889 | qA["split"] = (-(*it_split).shares() * xr).convert(fraction).toString(); | ||
749 | qA["rank"] = '1'; | 890 | qA["rank"] = '2'; | ||
750 | } else { | 891 | } else { | ||
751 | //this applies when the transaction has only 2 splits, or each split is going to be | 892 | //this applies when the transaction has only 2 splits, or each split is going to be | ||
752 | //shown separately, eg. transactions by category | 893 | //shown separately, eg. transactions by category | ||
753 | 894 | | |||
754 | qA["split"] = ""; | 895 | qA["split"] = ""; | ||
755 | qA["rank"] = '0'; | 896 | qA["rank"] = '1'; | ||
756 | } | 897 | } | ||
757 | 898 | | |||
758 | qA ["memo"] = (*it_split).memo(); | 899 | qA ["memo"] = (*it_split).memo(); | ||
759 | 900 | | |||
760 | // if different from base currency and not converting | 901 | // if different from base currency and not converting | ||
761 | // show the currency of the split | 902 | // show the currency of the split | ||
762 | if (splitAcc.currencyId() != file->baseCurrency().id()) { | 903 | if (splitAcc.currencyId() != file->baseCurrency().id()) { | ||
763 | if (!report.isConvertCurrency()) { | 904 | if (!report.isConvertCurrency()) { | ||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Line(s) | |||||
807 | } | 948 | } | ||
808 | 949 | | |||
809 | if (m_config.includes(splitAcc) && use_transfers && | 950 | if (m_config.includes(splitAcc) && use_transfers && | ||
810 | !(splitAcc.isInvest() && include_me)) { // otherwise stock split is displayed twice in report | 951 | !(splitAcc.isInvest() && include_me)) { // otherwise stock split is displayed twice in report | ||
811 | if (! splitAcc.isIncomeExpense()) { | 952 | if (! splitAcc.isIncomeExpense()) { | ||
812 | //multiply by currency and convert to lowest fraction | 953 | //multiply by currency and convert to lowest fraction | ||
813 | qS["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); | 954 | qS["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); | ||
814 | 955 | | |||
815 | qS["rank"] = '0'; | 956 | qS["rank"] = '1'; | ||
816 | 957 | | |||
817 | qS["account"] = splitAcc.name(); | 958 | qS["account"] = splitAcc.name(); | ||
818 | qS["accountid"] = splitAcc.id(); | 959 | qS["accountid"] = splitAcc.id(); | ||
819 | qS["topaccount"] = splitAcc.topParentName(); | 960 | qS["topaccount"] = splitAcc.topParentName(); | ||
820 | 961 | | |||
821 | qS["category"] = ((*it_split).shares().isNegative()) | 962 | qS["category"] = ((*it_split).shares().isNegative()) | ||
822 | ? i18n("Transfer to %1", a_fullname) | 963 | ? i18n("Transfer to %1", a_fullname) | ||
823 | : i18n("Transfer from %1", a_fullname); | 964 | : i18n("Transfer from %1", a_fullname); | ||
▲ Show 20 Lines • Show All 120 Lines • ▼ Show 20 Line(s) | 1049 | for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) { | |||
944 | //starting balance | 1085 | //starting balance | ||
945 | // don't show currency if we're converting or if it's not foreign | 1086 | // don't show currency if we're converting or if it's not foreign | ||
946 | qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id(); | 1087 | qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id(); | ||
947 | 1088 | | |||
948 | qA["accountid"] = account.id(); | 1089 | qA["accountid"] = account.id(); | ||
949 | qA["account"] = account.name(); | 1090 | qA["account"] = account.name(); | ||
950 | qA["topaccount"] = account.topParentName(); | 1091 | qA["topaccount"] = account.topParentName(); | ||
951 | qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); | 1092 | qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); | ||
952 | qA["rank"] = "-2"; | 1093 | qA["rank"] = "0"; | ||
953 | 1094 | | |||
954 | qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | 1095 | qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | ||
955 | if (account.isInvest()) { | 1096 | if (account.isInvest()) { | ||
956 | qA["shares"] = startShares.toString(); | 1097 | qA["shares"] = startShares.toString(); | ||
957 | } | 1098 | } | ||
958 | 1099 | | |||
959 | qA["postdate"] = strStartDate; | 1100 | qA["postdate"] = strStartDate; | ||
960 | qA["balance"] = startBalance.convert(fraction).toString(); | 1101 | qA["balance"] = startBalance.convert(fraction).toString(); | ||
961 | qA["value"].clear(); | 1102 | qA["value"].clear(); | ||
962 | qA["id"] = 'A'; | 1103 | qA["id"] = 'A'; | ||
963 | m_rows += qA; | 1104 | m_rows += qA; | ||
964 | 1105 | | |||
965 | //ending balance | 1106 | //ending balance | ||
966 | qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | 1107 | qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | ||
967 | 1108 | | |||
968 | if (account.isInvest()) { | 1109 | if (account.isInvest()) { | ||
969 | qA["shares"] = endShares.toString(); | 1110 | qA["shares"] = endShares.toString(); | ||
970 | } | 1111 | } | ||
971 | 1112 | | |||
972 | qA["postdate"] = strEndDate; | 1113 | qA["postdate"] = strEndDate; | ||
973 | qA["balance"] = endBalance.toString(); | 1114 | qA["balance"] = endBalance.toString(); | ||
1115 | qA["rank"] = "3"; | ||||
974 | qA["id"] = 'Z'; | 1116 | qA["id"] = 'Z'; | ||
975 | m_rows += qA; | 1117 | m_rows += qA; | ||
976 | } | 1118 | } | ||
977 | } | 1119 | } | ||
978 | 1120 | | |||
979 | void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result) const | 1121 | MyMoneyMoney QueryTable::helperROI(const MyMoneyMoney &buys, const MyMoneyMoney &sells, const MyMoneyMoney &startingBal, const MyMoneyMoney &endingBal, const MyMoneyMoney &cashIncome) const | ||
1122 | { | ||||
1123 | MyMoneyMoney returnInvestment; | ||||
1124 | if (!buys.isZero() || !startingBal.isZero()) { | ||||
1125 | returnInvestment = (sells + buys + cashIncome + endingBal - startingBal) / (startingBal - buys); | ||||
1126 | returnInvestment = returnInvestment.convert(10000); | ||||
1127 | } else | ||||
1128 | returnInvestment = MyMoneyMoney(); // if no investment then no return on investment | ||||
1129 | return returnInvestment; | ||||
1130 | } | ||||
1131 | | ||||
1132 | MyMoneyMoney QueryTable::helperIRR(const CashFlowList &all) const | ||||
1133 | { | ||||
1134 | MyMoneyMoney annualReturn; | ||||
1135 | try { | ||||
1136 | double irr = all.IRR(); | ||||
1137 | #ifdef Q_CC_MSVC | ||||
1138 | annualReturn = MyMoneyMoney(_isnan(irr) ? 0 : irr, 10000); | ||||
1139 | #else | ||||
1140 | annualReturn = MyMoneyMoney(std::isnan(irr) ? 0 : irr, 10000); | ||||
1141 | #endif | ||||
1142 | } catch (QString e) { | ||||
1143 | qDebug() << e; | ||||
1144 | } | ||||
1145 | return annualReturn; | ||||
1146 | } | ||||
1147 | | ||||
1148 | void QueryTable::constructPerformanceRow(const ReportAccount& account, TableRow& result, CashFlowList &all) const | ||||
980 | { | 1149 | { | ||
981 | MyMoneyFile* file = MyMoneyFile::instance(); | 1150 | MyMoneyFile* file = MyMoneyFile::instance(); | ||
982 | MyMoneySecurity security; | 1151 | MyMoneySecurity security; | ||
983 | 1152 | | |||
984 | //get fraction depending on type of account | 1153 | //get fraction depending on type of account | ||
985 | int fraction = account.currency().smallestAccountFraction(); | 1154 | int fraction = account.currency().smallestAccountFraction(); | ||
986 | 1155 | | |||
987 | // | 1156 | // | ||
▲ Show 20 Lines • Show All 99 Lines • ▼ Show 20 Line(s) | 1255 | else if (transactionType == MyMoneySplit::ReinvestDividend) { | |||
1087 | value = interestSplits.first().value() * price; | 1256 | value = interestSplits.first().value() * price; | ||
1088 | reinvestincome += CashFlowListItem((*it_transaction).postDate(), -value); | 1257 | reinvestincome += CashFlowListItem((*it_transaction).postDate(), -value); | ||
1089 | } else if (transactionType == MyMoneySplit::Dividend || transactionType == MyMoneySplit::Yield) | 1258 | } else if (transactionType == MyMoneySplit::Dividend || transactionType == MyMoneySplit::Yield) | ||
1090 | cashincome += CashFlowListItem((*it_transaction).postDate(), value); | 1259 | cashincome += CashFlowListItem((*it_transaction).postDate(), value); | ||
1091 | ++it_transaction; | 1260 | ++it_transaction; | ||
1092 | } | 1261 | } | ||
1093 | // Note that reinvested dividends are not included , because these do not | 1262 | // Note that reinvested dividends are not included , because these do not | ||
1094 | // represent a cash flow event. | 1263 | // represent a cash flow event. | ||
1095 | CashFlowList all; | | |||
1096 | all += buys; | 1264 | all += buys; | ||
1097 | all += sells; | 1265 | all += sells; | ||
1098 | all += cashincome; | 1266 | all += cashincome; | ||
1099 | all += CashFlowListItem(startingDate, -startingBal); | 1267 | all += CashFlowListItem(startingDate, -startingBal); | ||
1100 | all += CashFlowListItem(endingDate, endingBal); | 1268 | all += CashFlowListItem(endingDate, endingBal); | ||
1101 | 1269 | | |||
1102 | MyMoneyMoney returnInvestment; | | |||
1103 | MyMoneyMoney buysTotal = buys.total(); | 1270 | MyMoneyMoney buysTotal = buys.total(); | ||
1104 | MyMoneyMoney sellsTotal = sells.total(); | 1271 | MyMoneyMoney sellsTotal = sells.total(); | ||
1105 | MyMoneyMoney cashIncomeTotal = cashincome.total(); | 1272 | MyMoneyMoney cashIncomeTotal = cashincome.total(); | ||
1106 | MyMoneyMoney reinvestIncomeTotal = reinvestincome.total(); | 1273 | MyMoneyMoney reinvestIncomeTotal = reinvestincome.total(); | ||
1107 | 1274 | | |||
1108 | if (!buysTotal.isZero() || !startingBal.isZero()) { | 1275 | MyMoneyMoney returnInvestment = helperROI(buysTotal, sellsTotal, startingBal, endingBal, cashIncomeTotal); | ||
1109 | returnInvestment = (sellsTotal + buysTotal + cashIncomeTotal + endingBal - startingBal) / (startingBal - buysTotal); | 1276 | MyMoneyMoney annualReturn = helperIRR(all); | ||
1110 | returnInvestment = returnInvestment.convert(10000); | | |||
1111 | } else | | |||
1112 | returnInvestment = MyMoneyMoney(); // if no investment then no return on investment | | |||
1113 | | ||||
1114 | MyMoneyMoney annualReturn; | | |||
1115 | try { | | |||
1116 | double irr = all.IRR(); | | |||
1117 | #ifdef Q_CC_MSVC | | |||
1118 | annualReturn = MyMoneyMoney(_isnan(irr) ? 0 : irr, 10000); | | |||
1119 | #else | | |||
1120 | annualReturn = MyMoneyMoney(std::isnan(irr) ? 0 : irr, 10000); | | |||
1121 | #endif | | |||
1122 | } catch (QString e) { | | |||
1123 | qDebug() << e; | | |||
1124 | } | | |||
1125 | 1277 | | |||
1126 | // check if there are any meaningfull values before adding them to results | 1278 | // check if there are any meaningfull values before adding them to results | ||
1127 | if (!(buysTotal.isZero() && sellsTotal.isZero() && | 1279 | if (!(buysTotal.isZero() && sellsTotal.isZero() && | ||
1128 | cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero() && | 1280 | cashIncomeTotal.isZero() && reinvestIncomeTotal.isZero() && | ||
1129 | startingBal.isZero() && endingBal.isZero())) { | 1281 | startingBal.isZero() && endingBal.isZero())) { | ||
1130 | result["return"] = annualReturn.toString(); | 1282 | result["return"] = annualReturn.toString(); | ||
1131 | result["returninvestment"] = returnInvestment.toString(); | 1283 | result["returninvestment"] = returnInvestment.toString(); | ||
1132 | result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType()); | 1284 | result["equitytype"] = KMyMoneyUtils::securityTypeToString(security.securityType()); | ||
▲ Show 20 Lines • Show All 116 Lines • ▼ Show 20 Line(s) | |||||
1249 | 1401 | | |||
1250 | void QueryTable::constructAccountTable() | 1402 | void QueryTable::constructAccountTable() | ||
1251 | { | 1403 | { | ||
1252 | MyMoneyFile* file = MyMoneyFile::instance(); | 1404 | MyMoneyFile* file = MyMoneyFile::instance(); | ||
1253 | 1405 | | |||
1254 | //make sure we have all subaccounts of investment accounts | 1406 | //make sure we have all subaccounts of investment accounts | ||
1255 | includeInvestmentSubAccounts(); | 1407 | includeInvestmentSubAccounts(); | ||
1256 | 1408 | | |||
1409 | | ||||
1410 | QMap<QString, CashFlowList> topAccounts; // for total calculation | ||||
1257 | QList<MyMoneyAccount> accounts; | 1411 | QList<MyMoneyAccount> accounts; | ||
1258 | file->accountList(accounts); | 1412 | file->accountList(accounts); | ||
1259 | for (auto it_account = accounts.constBegin(); it_account != accounts.constEnd(); ++it_account) { | 1413 | for (auto it_account = accounts.constBegin(); it_account != accounts.constEnd(); ++it_account) { | ||
1260 | // Note, "Investment" accounts are never included in account rows because | 1414 | // Note, "Investment" accounts are never included in account rows because | ||
1261 | // they don't contain anything by themselves. In reports, they are only | 1415 | // they don't contain anything by themselves. In reports, they are only | ||
1262 | // useful as a "topaccount" aggregator of stock accounts | 1416 | // useful as a "topaccount" aggregator of stock accounts | ||
1263 | if ((*it_account).isAssetLiability() && m_config.includes((*it_account)) && (*it_account).accountType() != MyMoneyAccount::Investment) { | 1417 | if ((*it_account).isAssetLiability() && m_config.includes((*it_account)) && (*it_account).accountType() != MyMoneyAccount::Investment) { | ||
1264 | // don't add the account if it is closed. In fact, the business logic | 1418 | // don't add the account if it is closed. In fact, the business logic | ||
1265 | // should prevent that an account can be closed with a balance not equal | 1419 | // should prevent that an account can be closed with a balance not equal | ||
1266 | // to zero, but we never know. | 1420 | // to zero, but we never know. | ||
1267 | MyMoneyMoney shares = file->balance((*it_account).id(), m_config.toDate()); | 1421 | MyMoneyMoney shares = file->balance((*it_account).id(), m_config.toDate()); | ||
1268 | if (shares.isZero() && (*it_account).isClosed()) | 1422 | if (shares.isZero() && (*it_account).isClosed()) | ||
1269 | continue; | 1423 | continue; | ||
1270 | 1424 | | |||
1271 | ReportAccount account(*it_account); | 1425 | ReportAccount account(*it_account); | ||
1272 | TableRow qaccountrow; | 1426 | TableRow qaccountrow; | ||
1427 | CashFlowList accountCashflow; // for total calculation | ||||
1273 | if (m_config.queryColumns() == MyMoneyReport::eQCperformance) { | 1428 | if (m_config.queryColumns() == MyMoneyReport::eQCperformance) { | ||
1274 | constructPerformanceRow(account, qaccountrow); | 1429 | constructPerformanceRow(account, qaccountrow, accountCashflow); | ||
1275 | } else if (m_config.queryColumns() == MyMoneyReport::eQCcapitalgain) { | 1430 | } else if (m_config.queryColumns() == MyMoneyReport::eQCcapitalgain) { | ||
1276 | constructCapitalGainRow(account, qaccountrow); | 1431 | constructCapitalGainRow(account, qaccountrow); | ||
1277 | } else | 1432 | } else | ||
1278 | qaccountrow["equitytype"].clear(); | 1433 | qaccountrow["equitytype"].clear(); | ||
1279 | 1434 | | |||
1280 | if (qaccountrow.isEmpty()) // don't add the account if there are no calculated values | 1435 | if (qaccountrow.isEmpty()) // don't add the account if there are no calculated values | ||
1281 | continue; | 1436 | continue; | ||
1282 | 1437 | | |||
1283 | // help for sort and render functions | 1438 | // help for sort and render functions | ||
1284 | qaccountrow["rank"] = '0'; | 1439 | qaccountrow["rank"] = '1'; | ||
1285 | // | 1440 | // | ||
1286 | // Handle currency conversion | 1441 | // Handle currency conversion | ||
1287 | // | 1442 | // | ||
1288 | 1443 | | |||
1289 | MyMoneyMoney displayprice(1, 1); | 1444 | MyMoneyMoney displayprice(1, 1); | ||
1290 | if (m_config.isConvertCurrency()) { | 1445 | if (m_config.isConvertCurrency()) { | ||
1291 | // display currency is base currency, so set the price | 1446 | // display currency is base currency, so set the price | ||
1292 | if (account.isForeignCurrency()) | 1447 | if (account.isForeignCurrency()) | ||
Show All 30 Lines | |||||
1323 | 1478 | | |||
1324 | if (iid.isEmpty()) | 1479 | if (iid.isEmpty()) | ||
1325 | qaccountrow["institution"] = i18nc("No institution", "None"); | 1480 | qaccountrow["institution"] = i18nc("No institution", "None"); | ||
1326 | else | 1481 | else | ||
1327 | qaccountrow["institution"] = file->institution(iid).name(); | 1482 | qaccountrow["institution"] = file->institution(iid).name(); | ||
1328 | 1483 | | |||
1329 | qaccountrow["type"] = KMyMoneyUtils::accountTypeToString((*it_account).accountType()); | 1484 | qaccountrow["type"] = KMyMoneyUtils::accountTypeToString((*it_account).accountType()); | ||
1330 | 1485 | | |||
1486 | // assuming that that report is grouped by topaccount | ||||
1487 | if (m_config.queryColumns() == MyMoneyReport::eQCperformance) { | ||||
1488 | if (!topAccounts.contains(qaccountrow["topaccount"])) | ||||
1489 | topAccounts.insert(qaccountrow["topaccount"], accountCashflow); // create cashflow for unknown account... | ||||
1490 | else | ||||
1491 | topAccounts[qaccountrow["topaccount"]] += accountCashflow; // ...or add cashflow for known account | ||||
1492 | } | ||||
1493 | | ||||
1331 | m_rows += qaccountrow; | 1494 | m_rows += qaccountrow; | ||
1332 | } | 1495 | } | ||
1333 | } | 1496 | } | ||
1497 | | ||||
1498 | if (m_config.queryColumns() == MyMoneyReport::eQCperformance) { | ||||
1499 | TableRow qtotalsrow; | ||||
1500 | qtotalsrow["rank"] = "4"; // add identification of row as total | ||||
1501 | CashFlowList grandCashflow; | ||||
1502 | | ||||
1503 | // convert map of top accounts with cashflows to TableRow | ||||
1504 | for (QMap<QString, CashFlowList>::iterator topAccount = topAccounts.begin(); topAccount != topAccounts.end(); ++topAccount) { | ||||
1505 | qtotalsrow["topaccount"] = topAccount.key(); | ||||
1506 | qtotalsrow["return"] = helperIRR(topAccount.value()).toString(); | ||||
1507 | grandCashflow += topAccount.value(); // cumulative sum of cashflows of each topaccount | ||||
1508 | m_rows += qtotalsrow; // rows aren't sorted yet, so no problem with adding them randomly at the end | ||||
1509 | } | ||||
1510 | qtotalsrow["topaccount"] = ""; // empty topaccount because it's grand cashflow | ||||
1511 | qtotalsrow["return"] = helperIRR(grandCashflow).toString(); | ||||
1512 | m_rows += qtotalsrow; | ||||
1513 | } | ||||
1514 | | ||||
1334 | } | 1515 | } | ||
1335 | 1516 | | |||
1336 | void QueryTable::constructSplitsTable() | 1517 | void QueryTable::constructSplitsTable() | ||
1337 | { | 1518 | { | ||
1338 | MyMoneyFile* file = MyMoneyFile::instance(); | 1519 | MyMoneyFile* file = MyMoneyFile::instance(); | ||
1339 | 1520 | | |||
1340 | //make sure we have all subaccounts of investment accounts | 1521 | //make sure we have all subaccounts of investment accounts | ||
1341 | includeInvestmentSubAccounts(); | 1522 | includeInvestmentSubAccounts(); | ||
▲ Show 20 Lines • Show All 175 Lines • ▼ Show 20 Line(s) | 1621 | do { | |||
1517 | qS["topcategory"] = splitAcc.topParentName(); | 1698 | qS["topcategory"] = splitAcc.topParentName(); | ||
1518 | 1699 | | |||
1519 | // only include the configured accounts | 1700 | // only include the configured accounts | ||
1520 | if (include_me) { | 1701 | if (include_me) { | ||
1521 | // add the "summarized" split transaction | 1702 | // add the "summarized" split transaction | ||
1522 | // this is the sub-total of the split detail | 1703 | // this is the sub-total of the split detail | ||
1523 | // convert to lowest fraction | 1704 | // convert to lowest fraction | ||
1524 | qA["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); | 1705 | qA["value"] = ((*it_split).shares() * xr).convert(fraction).toString(); | ||
1525 | qA["rank"] = '0'; | 1706 | qA["rank"] = '1'; | ||
1526 | 1707 | | |||
1527 | //fill in account information | 1708 | //fill in account information | ||
1528 | if (! splitAcc.isIncomeExpense() && it_split != myBegin) { | 1709 | if (! splitAcc.isIncomeExpense() && it_split != myBegin) { | ||
1529 | qA["account"] = ((*it_split).shares().isNegative()) ? | 1710 | qA["account"] = ((*it_split).shares().isNegative()) ? | ||
1530 | i18n("Transfer to %1", myBeginAcc.fullName()) | 1711 | i18n("Transfer to %1", myBeginAcc.fullName()) | ||
1531 | : i18n("Transfer from %1", myBeginAcc.fullName()); | 1712 | : i18n("Transfer from %1", myBeginAcc.fullName()); | ||
1532 | } else if (it_split == myBegin) { | 1713 | } else if (it_split == myBegin) { | ||
1533 | //handle the main split | 1714 | //handle the main split | ||
▲ Show 20 Lines • Show All 115 Lines • ▼ Show 20 Line(s) | 1794 | for (it_account = accts.constBegin(); it_account != accts.constEnd(); ++it_account) { | |||
1649 | //starting balance | 1830 | //starting balance | ||
1650 | // don't show currency if we're converting or if it's not foreign | 1831 | // don't show currency if we're converting or if it's not foreign | ||
1651 | qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id(); | 1832 | qA["currency"] = (m_config.isConvertCurrency() || ! account.isForeignCurrency()) ? "" : account.currency().id(); | ||
1652 | 1833 | | |||
1653 | qA["accountid"] = account.id(); | 1834 | qA["accountid"] = account.id(); | ||
1654 | qA["account"] = account.name(); | 1835 | qA["account"] = account.name(); | ||
1655 | qA["topaccount"] = account.topParentName(); | 1836 | qA["topaccount"] = account.topParentName(); | ||
1656 | qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); | 1837 | qA["institution"] = institution.isEmpty() ? i18n("No Institution") : file->institution(institution).name(); | ||
1657 | qA["rank"] = "-2"; | 1838 | qA["rank"] = "0"; | ||
1658 | 1839 | | |||
1659 | qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | 1840 | qA["price"] = startPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | ||
1660 | if (account.isInvest()) { | 1841 | if (account.isInvest()) { | ||
1661 | qA["shares"] = startShares.toString(); | 1842 | qA["shares"] = startShares.toString(); | ||
1662 | } | 1843 | } | ||
1663 | 1844 | | |||
1664 | qA["postdate"] = strStartDate; | 1845 | qA["postdate"] = strStartDate; | ||
1665 | qA["balance"] = startBalance.convert(fraction).toString(); | 1846 | qA["balance"] = startBalance.convert(fraction).toString(); | ||
1666 | qA["value"].clear(); | 1847 | qA["value"].clear(); | ||
1667 | qA["id"] = 'A'; | 1848 | qA["id"] = 'A'; | ||
1668 | m_rows += qA; | 1849 | m_rows += qA; | ||
1669 | 1850 | | |||
1851 | qA["rank"] = "3"; | ||||
1670 | //ending balance | 1852 | //ending balance | ||
1671 | qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | 1853 | qA["price"] = endPrice.convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())).toString(); | ||
1672 | 1854 | | |||
1673 | if (account.isInvest()) { | 1855 | if (account.isInvest()) { | ||
1674 | qA["shares"] = endShares.toString(); | 1856 | qA["shares"] = endShares.toString(); | ||
1675 | } | 1857 | } | ||
1676 | 1858 | | |||
1677 | qA["postdate"] = strEndDate; | 1859 | qA["postdate"] = strEndDate; | ||
Show All 9 Lines |