/*************************************************************************** khomeview.cpp - description ------------------- begin : Tue Jan 22 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "khomeview.h" #include "../kmymoneyutils.h" #include "../kmymoneyglobalsettings.h" #include "../mymoney/mymoneyfile.h" #include "../mymoney/mymoneyforecast.h" #include "../kmymoney2.h" #include "../reports/kreportchartview.h" #include "../reports/pivottable.h" #include "../reports/pivotgrid.h" #include "../reports/reportaccount.h" #include "../kmymoneyglobalsettings.h" #define VIEW_LEDGER "ledger" #define VIEW_SCHEDULE "schedule" #define VIEW_WELCOME "welcome" #define VIEW_HOME "home" #define VIEW_REPORTS "reports" // in KOffice version < 1.5 KDCHART_PROPSET_NORMAL_DATA was a static const // but in 1.5 this has been changed into a #define'd value. So we have to // make sure, we use the right one. #ifndef KDCHART_PROPSET_NORMAL_DATA #define KMM_KDCHART_PROPSET_NORMAL_DATA KDChartParams::KDCHART_PROPSET_NORMAL_DATA #else #define KMM_KDCHART_PROPSET_NORMAL_DATA KDCHART_PROPSET_NORMAL_DATA #endif using namespace reports; class KHomeView::Private { public: Private() {} void addNameIndex(QMap &idx, const MyMoneyAccount& account); }; void KHomeView::Private::addNameIndex(QMap &idx, const MyMoneyAccount& account) { QString key = account.name(); if(idx[key].id().isEmpty()) { idx[key] = account; //take care of accounts with duplicate names } else if(idx[key].id() != account.id()) { key = account.name() + "[%1]"; int dup = 2; while(!idx[key.arg(dup)].id().isEmpty() && idx[key.arg(dup)].id() != account.id()) ++dup; idx[key.arg(dup)] = account; } } KHomeView::KHomeView(QWidget *parent, const char *name ) : KMyMoneyViewBase(parent, name, i18n("Home")), d(new Private), m_showAllSchedules(false), m_needReload(true) { m_part = new KHTMLPart(this, "htmlpart_km2"); addWidget(m_part->view()); m_filename = KMyMoneyUtils::findResource("appdata", QString("html/home%1.html")); // m_part->openURL(m_filename); connect(m_part->browserExtension(), SIGNAL(openURLRequest(const KURL&, const KParts::URLArgs&)), this, SLOT(slotOpenURL(const KURL&, const KParts::URLArgs&))); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); } KHomeView::~KHomeView() { // if user wants to remember the font size, store it here if (KMyMoneyGlobalSettings::rememberFontSize()) { KMyMoneyGlobalSettings::setFontSizePercentage(m_part->zoomFactor()); //kdDebug() << "Storing font size: " << m_part->zoomFactor() << endl; KMyMoneyGlobalSettings::self()->writeConfig(); } delete d; } void KHomeView::slotLoadView(void) { m_needReload = true; if(isVisible()) { loadView(); m_needReload = false; } } void KHomeView::show(void) { if(m_needReload) { loadView(); m_needReload = false; } QWidget::show(); } void KHomeView::slotPrintView(void) { if(m_part && m_part->view()) m_part->view()->print(); } void KHomeView::loadView(void) { m_part->setZoomFactor( KMyMoneyGlobalSettings::fontSizePercentage() ); //kdDebug() << "Setting font size: " << m_part->zoomFactor() << endl; QValueList list; MyMoneyFile::instance()->accountList(list); if(list.count() == 0) { m_part->openURL(m_filename); #if 0 // (ace) I am experimenting with replacing links in the // html depending on the state of the engine. It's not // working. That's why it's #if0'd out. DOM::Element e = m_part->document().getElementById("test"); if ( e.isNull() ) { qDebug("Element id=test not found"); } else { qDebug("Element id=test found!"); QString tagname = e.tagName().string(); qDebug("%s",tagname.latin1()); qDebug("%s id=%s",e.tagName().string().latin1(),e.getAttribute("id").string().latin1()); // Find the character data node DOM::Node n = e.firstChild(); while (!n.isNull()) { qDebug("Child type %u",static_cast(n.nodeType())); if ( n.nodeType() == DOM::Node::TEXT_NODE ) { DOM::Text t = n; t.setData("Success!!"); e.replaceChild(n,t); m_part->document().setDesignMode(true); m_part->document().importNode(e,true); m_part->document().updateRendering(); qDebug("Data is now %s",t.data().string().latin1()); } n = n.nextSibling(); } } #endif } else { //clear the forecast flag so it will be reloaded m_forecast.setForecastDone(false); QString filename = KGlobal::dirs()->findResource("appdata", "html/kmymoney2.css"); QString header = QString("\n\n").arg(filename); header += KMyMoneyUtils::variableCSS(); header += "\n"; QString footer = "\n"; m_part->begin(); m_part->write(header); m_part->write(QString("
%1
").arg(i18n("Your Financial Summary"))); QStringList settings = KMyMoneyGlobalSettings::itemList(); QStringList::ConstIterator it; for(it = settings.begin(); it != settings.end(); ++it) { int option = (*it).toInt(); if(option > 0) { switch(option) { case 1: // payments showPayments(); break; case 2: // preferred accounts showAccounts(Preferred, i18n("Preferred Accounts")); break; case 3: // payment accounts // Check if preferred accounts are shown separately if(settings.find("2") == settings.end()) { showAccounts(static_cast (Payment | Preferred), i18n("Payment Accounts")); } else { showAccounts(Payment, i18n("Payment Accounts")); } break; case 4: // favorite reports showFavoriteReports(); break; case 5: // forecast showForecast(); break; case 6: // net worth graph over all accounts showNetWorthGraph(); break; case 8: // assets and liabilities showAssetsLiabilities(); break; case 9: // budget showBudget(); break; case 10: // cash flow summary showCashFlowSummary(); break; } m_part->write("
 
\n"); } } m_part->write("
"); m_part->write(link(VIEW_WELCOME, QString()) + i18n("Show KMyMoney welcome page") + linkend()); m_part->write("
"); m_part->write("
"); m_part->write(footer); m_part->end(); } } void KHomeView::showNetWorthGraph(void) { #ifdef HAVE_KDCHART m_part->write(QString("
%1
\n
 
\n").arg(i18n("Networth Forecast"))); MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::eMonths, MyMoneyTransactionFilter::userDefined, // overridden by the setDateFilter() call below MyMoneyReport::eDetailTotal, i18n("Networth Forecast"), i18n("Generated Report")); reportCfg.setChartByDefault(true); reportCfg.setChartGridLines(false); reportCfg.setChartDataLabels(false); reportCfg.setChartType(MyMoneyReport::eChartLine); reportCfg.setIncludingSchedules( false ); reportCfg.addAccountGroup(MyMoneyAccount::Asset); reportCfg.addAccountGroup(MyMoneyAccount::Liability); reportCfg.setColumnsAreDays( true ); reportCfg.setConvertCurrency( true ); reportCfg.setIncludingForecast( true ); reportCfg.setDateFilter(QDate::currentDate(),QDate::currentDate().addDays(+90)); reports::PivotTable table(reportCfg); reports::KReportChartView* chartWidget = new reports::KReportChartView(0, 0); table.drawChart(*chartWidget); chartWidget->params()->setLineMarker(false); chartWidget->params()->setLegendPosition(KDChartParams::NoLegend); chartWidget->params()->setLineWidth(2); chartWidget->params()->setDataColor(0, KGlobalSettings::textColor()); // draw future values in a different line style KDChartPropertySet propSetFutureValue("future value", KMM_KDCHART_PROPSET_NORMAL_DATA); propSetFutureValue.setLineStyle(KDChartPropertySet::OwnID, Qt::DotLine); const int idPropFutureValue = chartWidget->params()->registerProperties(propSetFutureValue); //KDChartPropertySet propSetLastValue("last value", idPropFutureValue); //propSetLastValue.setExtraLinesAlign(KDChartPropertySet::OwnID, Qt::AlignLeft | Qt::AlignBottom); //propSetLastValue.setExtraLinesWidth(KDChartPropertySet::OwnID, -4); //propSetLastValue.setExtraLinesColor(KDChartPropertySet::OwnID, KMyMoneyGlobalSettings::listGridColor()); // propSetLastValue.setShowMarker(KDChartPropertySet::OwnID, true); // propSetLastValue.setMarkerStyle(KDChartPropertySet::OwnID, KDChartParams::LineMarkerDiamond); //const int idPropLastValue = chartWidget->params()->registerProperties(propSetLastValue); for(int iCell = 0; iCell < 90; ++iCell) { chartWidget->setProperty(0, iCell, idPropFutureValue); } //chartWidget->setProperty(0, 10, idPropLastValue); // Adjust the size if(width() < chartWidget->width()) { int nh; nh = (width()*chartWidget->height() ) / chartWidget->width(); chartWidget->resize(width()-80, nh); } QPixmap pm(chartWidget->width(), chartWidget->height()); pm.fill(KGlobalSettings::baseColor()); QPainter p(&pm); chartWidget->paintTo(p); QByteArray ba; QBuffer buffer( ba ); buffer.open( IO_WriteOnly ); pm.save( &buffer, "PNG" ); // writes pixmap into ba in PNG format m_part->write(""); m_part->write(""); m_part->write(QString("").arg(KCodecs::base64Encode(ba))); m_part->write(""); m_part->write("
\"Networth\"
"); delete chartWidget; #endif } void KHomeView::showPayments(void) { MyMoneyFile* file = MyMoneyFile::instance(); QValueList overdues; QValueList schedule; int i = 0; //if forecast has not been executed yet, do it. if(!m_forecast.isForecastDone()) doForecast(); schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate::currentDate(), QDate::currentDate().addMonths(1)); overdues = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true); if(schedule.empty() && overdues.empty()) return; // HACK // Remove the finished schedules QValueList::Iterator d_it; for (d_it=schedule.begin(); d_it!=schedule.end();) { // FIXME cleanup old code // if ((*d_it).isFinished() || (*d_it).nextPayment((*d_it).lastPayment()) == QDate()) if ((*d_it).isFinished()) { d_it = schedule.remove(d_it); continue; } ++d_it; } for (d_it=overdues.begin(); d_it!=overdues.end();) { // FIXME cleanup old code // if ((*d_it).isFinished() || (*d_it).nextPayment((*d_it).lastPayment()) == QDate()) if ((*d_it).isFinished()) { d_it = overdues.remove(d_it); continue; } ++d_it; } m_part->write("
"); m_part->write(QString("
%1
\n").arg(i18n("Payments"))); if(overdues.count() > 0) { m_part->write("
 
\n"); qBubbleSort(overdues); QValueList::Iterator it; QValueList::Iterator it_f; m_part->write(""); m_part->write(QString("\n").arg(showColoredAmount(i18n("Overdue payments"), true))); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); for(it = overdues.begin(); it != overdues.end(); ++it) { // determine number of overdue payments QDate nextDate = (*it).adjustedNextDueDate(); int cnt = 0; while(nextDate.isValid() && nextDate < QDate::currentDate()) { ++cnt; nextDate = (*it).nextPayment(nextDate); // for single occurence nextDate will not change, so we // better get out of here. if((*it).occurence() == MyMoneySchedule::OCCUR_ONCE) break; } m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); showPaymentEntry(*it, cnt); m_part->write(""); // make sure to not repeat overdues later again for(it_f = schedule.begin(); it_f != schedule.end();) { if((*it).id() == (*it_f).id()) { it_f = schedule.remove(it_f); continue; } ++it_f; } } m_part->write("
%1
"); m_part->write(i18n("Date")); m_part->write(""); m_part->write(i18n("Schedule")); m_part->write(""); m_part->write(i18n("Account")); m_part->write(""); m_part->write(i18n("Amount")); m_part->write(""); m_part->write(i18n("Balance after")); m_part->write("
"); } if(schedule.count() > 0) { qBubbleSort(schedule); // Extract todays payments if any QValueList todays; QValueList::Iterator t_it; for (t_it=schedule.begin(); t_it!=schedule.end();) { if ((*t_it).nextDueDate() == QDate::currentDate()) { todays.append(*t_it); (*t_it).setNextDueDate((*t_it).nextPayment((*t_it).nextDueDate())); //if nextDueDate is still currentDate then remove it from scheduled payments if ((*t_it).nextDueDate() == QDate::currentDate()) { t_it = schedule.remove(t_it); continue; } } ++t_it; } if (todays.count() > 0) { m_part->write("
 
\n"); m_part->write(""); m_part->write(QString("\n").arg(i18n("Today's payments"))); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); for(t_it = todays.begin(); t_it != todays.end(); ++t_it) { m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); showPaymentEntry(*t_it); m_part->write(""); } m_part->write("
%1
"); m_part->write(i18n("Date")); m_part->write(""); m_part->write(i18n("Schedule")); m_part->write(""); m_part->write(i18n("Account")); m_part->write(""); m_part->write(i18n("Amount")); m_part->write(""); m_part->write(i18n("Balance after")); m_part->write("
"); } if (schedule.count() > 0) { m_part->write("
 
\n"); QValueList::Iterator it; m_part->write(""); m_part->write(QString("\n").arg(i18n("Future payments"))); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); // show all or the first 6 entries int cnt; cnt = (m_showAllSchedules) ? -1 : 6; bool needMoreLess = m_showAllSchedules; QDate lastDate = QDate::currentDate().addMonths(1); qBubbleSort(schedule); do { it = schedule.begin(); if(it == schedule.end()) break; // if the next due date is invalid (schedule is finished) // we remove it from the list QDate nextDate = (*it).nextDueDate(); if(!nextDate.isValid()) { schedule.remove(it); continue; } if (nextDate > lastDate) break; if(cnt == 0) { needMoreLess = true; break; } if(cnt > 0) --cnt; m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); showPaymentEntry(*it); m_part->write(""); // for single occurence we have reported everything so we // better get out of here. if((*it).occurence() == MyMoneySchedule::OCCUR_ONCE) { schedule.remove(it); continue; } (*it).setNextDueDate((*it).nextPayment((*it).nextDueDate())); qBubbleSort(schedule); } while(1); if (needMoreLess) { m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); m_part->write(""); m_part->write(""); } m_part->write("
%1
"); m_part->write(i18n("Date")); m_part->write(""); m_part->write(i18n("Schedule")); m_part->write(""); m_part->write(i18n("Account")); m_part->write(""); m_part->write(i18n("Amount")); m_part->write(""); m_part->write(i18n("Balance after")); m_part->write("
"); if(m_showAllSchedules) { m_part->write(link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18n("Less...") + linkend()); } else { m_part->write(link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18n("More...") + linkend()); } m_part->write("
"); } } m_part->write("
"); } void KHomeView::showPaymentEntry(const MyMoneySchedule& sched, int cnt) { QString tmp; MyMoneyFile* file = MyMoneyFile::instance(); try { MyMoneyAccount acc = sched.account(); if(acc.id()) { MyMoneyTransaction t = sched.transaction(); // only show the entry, if it is still active // FIXME clean old code // if(!sched.isFinished() && sched.nextPayment(sched.lastPayment()) != QDate()) { if(!sched.isFinished()) { MyMoneySplit sp = t.splitByAccount(acc.id(), true); QString pathEnter, pathSkip; KGlobal::iconLoader()->loadIcon("key_enter", KIcon::Small, KIcon::SizeSmall, KIcon::DefaultState, &pathEnter); KGlobal::iconLoader()->loadIcon("player_fwd", KIcon::Small, KIcon::SizeSmall, KIcon::DefaultState, &pathSkip); //show payment date tmp = QString("") + KGlobal::locale()->formatDate(sched.adjustedNextDueDate(), true) + ""; if(pathEnter.length() > 0) tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=enter").arg(sched.id()), i18n("Enter schedule")) + QString("").arg(pathEnter) + linkend(); if(pathSkip.length() > 0) tmp += " " + link(VIEW_SCHEDULE, QString("?id=%1&mode=skip").arg(sched.id()), i18n("Skip schedule")) + QString("").arg(pathSkip) + linkend(); tmp += QString(" "); tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend(); //show quantity of payments overdue if any if(cnt > 1) tmp += i18n(" (%1 payments)").arg(cnt); //show account of the main split tmp += ""; tmp += QString(file->account(acc.id()).name()); //show amount of the schedule tmp += ""; const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(acc.currencyId()); QString amount = (sp.value()*cnt).formatMoney(acc, currency); amount.replace(" "," "); tmp += showColoredAmount(amount, (sp.value()*cnt).isNegative()) ; tmp += ""; //show balance after payments tmp += ""; MyMoneyMoney payment = MyMoneyMoney((sp.value()*cnt)); QDate paymentDate = QDate(sched.nextDueDate()); MyMoneyMoney balanceAfter = forecastPaymentBalance(acc, payment, paymentDate); QString balance = balanceAfter.formatMoney(acc, currency); balance.replace(" "," "); tmp += showColoredAmount(balance, balanceAfter.isNegative()); tmp += ""; // qDebug("paymentEntry = '%s'", tmp.latin1()); m_part->write(tmp); } } } catch(MyMoneyException* e) { qDebug("Unable to display schedule entry: %s", e->what().data()); delete e; } } void KHomeView::showAccounts(KHomeView::paymentTypeE type, const QString& header) { MyMoneyFile* file = MyMoneyFile::instance(); QValueList accounts; QValueList::Iterator it; QValueList::Iterator prevIt; QMap nameIdx; bool showClosedAccounts = kmymoney2->toggleAction("view_show_all_accounts")->isChecked(); // get list of all accounts file->accountList(accounts); for(it = accounts.begin(); it != accounts.end();) { prevIt = it; if(!(*it).isClosed() || showClosedAccounts) { switch((*it).accountType()) { case MyMoneyAccount::Expense: case MyMoneyAccount::Income: // never show a category account // Note: This might be different in a future version when // the homepage also shows category based information it = accounts.remove(it); break; // Asset and Liability accounts are only shown if they // have the preferred flag set case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Investment: // if preferred accounts are requested, then keep in list if((*it).value("PreferredAccount") != "Yes" || (type & Preferred) == 0) { it = accounts.remove(it); } break; // Check payment accounts. If payment and preferred is selected, // then always show them. If only payment is selected, then // show only if preferred flag is not set. case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::CreditCard: switch(type & (Payment | Preferred)) { case Payment: if((*it).value("PreferredAccount") == "Yes") it = accounts.remove(it); break; case Preferred: if((*it).value("PreferredAccount") != "Yes") it = accounts.remove(it); break; case Payment | Preferred: break; default: it = accounts.remove(it); break; } break; // filter all accounts that are not used on homepage views default: it = accounts.remove(it); break; } } else if((*it).isClosed() || (*it).isInvest()) { // don't show if closed or a stock account it = accounts.remove(it); } // if we still point to the same account we keep it in the list and move on ;-) if(prevIt == it) { d->addNameIndex(nameIdx, *it); ++it; } } if(accounts.count() > 0) { QString tmp; int i = 0; tmp = "
" + header + "
\n
 
\n"; m_part->write(tmp); m_part->write(""); m_part->write(""); //only show limit info if user chose to do so if(KMyMoneyGlobalSettings::showLimitInfo()) { m_part->write(""); } m_part->write(""); QMap::const_iterator it_m; for(it_m = nameIdx.begin(); it_m != nameIdx.end(); ++it_m) { m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); showAccountEntry(*it_m); m_part->write(""); } m_part->write("
"); m_part->write(i18n("Account")); m_part->write(""); m_part->write(i18n("Current Balance")); m_part->write(""); m_part->write(i18n("To Minimum Balance / Maximum Credit")); m_part->write("
"); } } void KHomeView::showAccountEntry(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneySecurity currency = file->currency(acc.currencyId()); MyMoneyMoney value; bool showLimit = KMyMoneyGlobalSettings::showLimitInfo(); if(acc.accountType() == MyMoneyAccount::Investment) { //investment accounts show the balances of all its subaccounts value = investmentBalance(acc); //investment accounts have no minimum balance showAccountEntry(acc, value, MyMoneyMoney(), showLimit); } else { //get balance for normal accounts value = file->balance(acc.id(), QDate::currentDate()); //if credit card or checkings account, show maximum credit if( acc.accountType() == MyMoneyAccount::CreditCard || acc.accountType() == MyMoneyAccount::Checkings ) { QString maximumCredit = acc.value("maxCreditAbsolute"); MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit); showAccountEntry(acc, value, value - maxCredit, showLimit); } else { //otherwise use minimum balance QString minimumBalance = acc.value("minBalanceAbsolute"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); showAccountEntry(acc, value, value - minBalance, showLimit); } } } void KHomeView::showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal) { MyMoneyFile* file = MyMoneyFile::instance(); QString tmp; MyMoneySecurity currency = file->currency(acc.currencyId()); QString amount; QString amountToMinBal; //format amounts amount = value.formatMoney(acc, currency); amount.replace(" "," "); if(showMinBal) { amountToMinBal = valueToMinBal.formatMoney(acc, currency); amountToMinBal.replace(" "," "); } tmp = QString("") + link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())) + acc.name() + linkend() + ""; //show account balance tmp += QString("%1").arg(showColoredAmount(amount, value.isNegative())); //show minimum balance column if requested if(showMinBal) { //if it is an investment, show minimum balance empty if(acc.accountType() == MyMoneyAccount::Investment) { tmp += QString(" "); } else { //show minimum balance entry tmp += QString("%1").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative())); } } // qDebug("accountEntry = '%s'", tmp.latin1()); m_part->write(tmp); } MyMoneyMoney KHomeView::investmentBalance(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyMoney value; value = file->balance(acc.id()); QValueList::const_iterator it_a; for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) { MyMoneyAccount stock = file->account(*it_a); try { MyMoneyMoney val; MyMoneyMoney balance = file->balance(stock.id()); MyMoneySecurity security = file->security(stock.currencyId()); MyMoneyPrice price = file->price(stock.currencyId(), security.tradingCurrency()); val = (balance * price.rate(security.tradingCurrency())).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())); // 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(MyMoneyException* e) { qWarning("%s", (QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e->what())).data()); delete e; } } return value; } void KHomeView::showFavoriteReports(void) { QValueList reports = MyMoneyFile::instance()->reportList(); if ( reports.count() > 0 ) { bool firstTime = 1; int row = 0; QValueList::const_iterator it_report = reports.begin(); while( it_report != reports.end() ) { if ( (*it_report).isFavorite() ) { if(firstTime) { m_part->write(QString("
%1
\n
 
\n").arg(i18n("Favorite Reports"))); m_part->write(""); m_part->write(""); firstTime = false; } m_part->write(QString("") .arg(row++ & 0x01 ? "even" : "odd") .arg(link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id()))) .arg((*it_report).name()) .arg(linkend()) .arg((*it_report).comment()) ); } ++it_report; } if(!firstTime) m_part->write("
"); m_part->write(i18n("Report")); m_part->write(""); m_part->write(i18n("Comment")); m_part->write("
%2%3%4%5
"); } } void KHomeView::showForecast(void) { QMap nameIdx; MyMoneyFile* file = MyMoneyFile::instance(); QValueList accList; // if forecast has not been executed yet, do it. if(!m_forecast.isForecastDone()) doForecast(); accList = m_forecast.accountList(); // add it to a map to have it ordered by name QValueList::const_iterator accList_t = accList.begin(); for ( ; accList_t != accList.end(); ++accList_t ) { d->addNameIndex(nameIdx, *accList_t); } if(nameIdx.count() > 0) { int i = 0; int colspan = 1; // get begin day int beginDay = QDate::currentDate().daysTo(m_forecast.beginForecastDate()); // if begin day is today skip to next cycle if(beginDay == 0) beginDay = m_forecast.accountsCycle(); // Now output header m_part->write(QString("
%1
\n
 
\n").arg(i18n("%1 Day Forecast").arg(m_forecast.forecastDays()))); m_part->write(""); m_part->write(""); int colWidth = 55/ (m_forecast.forecastDays() / m_forecast.accountsCycle()); for(i = 0; (i*m_forecast.accountsCycle() + beginDay) <= m_forecast.forecastDays(); ++i) { m_part->write(QString(""); colspan++; } m_part->write(""); // Now output entries i = 0; QMap::ConstIterator it_account; for(it_account = nameIdx.begin(); it_account != nameIdx.end(); ++it_account) { //MyMoneyAccount acc = (*it_n); m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); m_part->write(QString(""); int dropZero = -1; //account dropped below zero int dropMinimum = -1; //account dropped below minimum balance QString minimumBalance = (*it_account).value("minimumBalance"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); MyMoneySecurity currency; MyMoneyMoney forecastBalance; //change account to deep currency if account is an investment if((*it_account).isInvest()) { MyMoneySecurity underSecurity = file->security((*it_account).currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security((*it_account).currencyId()); } for (int f = beginDay; f <= m_forecast.forecastDays(); f += m_forecast.accountsCycle()) { forecastBalance = m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f)); QString amount; amount = forecastBalance.formatMoney( *it_account, currency); amount.replace(" "," "); m_part->write(QString("").arg(showColoredAmount(amount, forecastBalance.isNegative()))); } m_part->write(""); //Check if the account is going to be below zero or below the minimal balance in the forecast period //Check if the account is going to be below minimal balance dropMinimum = m_forecast.daysToMinimumBalance(*it_account); //Check if the account is going to be below zero in the future dropZero = m_forecast.daysToZeroBalance(*it_account); // spit out possible warnings QString msg; // if a minimum balance has been specified, an appropriate warning will // only be shown, if the drop below 0 is on a different day or not present if(dropMinimum != -1 && !minBalance.isZero() && (dropMinimum < dropZero || dropZero == -1)) { switch(dropMinimum) { case -1: break; case 0: msg = i18n("The balance of %1 is below the minimum balance %2 today.").arg((*it_account).name()).arg(minBalance.formatMoney(*it_account, currency)); msg = showColoredAmount(msg, true); break; default: msg = i18n("The balance of %1 will drop below the minimum balance %2 in %3 days.").arg((*it_account).name()).arg(minBalance.formatMoney(*it_account, currency)).arg(dropMinimum-1); msg = showColoredAmount(msg, true); break; } if(!msg.isEmpty()) { m_part->write(QString("").arg(msg).arg(colspan)); } } // a drop below zero is always shown msg = QString(); switch(dropZero) { case -1: break; case 0: if((*it_account).accountGroup() == MyMoneyAccount::Asset) { msg = i18n("The balance of %1 is below %2 today.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)); msg = showColoredAmount(msg, true); break; } if((*it_account).accountGroup() == MyMoneyAccount::Liability) { msg = i18n("The balance of %1 is above %2 today.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)); break; } break; default: if((*it_account).accountGroup() == MyMoneyAccount::Asset) { msg = i18n("The balance of %1 will drop below %2 in %3 days.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)).arg(dropZero); msg = showColoredAmount(msg, true); break; } if((*it_account).accountGroup() == MyMoneyAccount::Liability) { msg = i18n("The balance of %1 will raise above %2 in %3 days.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)).arg(dropZero); break; } } if(!msg.isEmpty()) { m_part->write(QString("").arg(msg).arg(colspan)); } } m_part->write("
"); m_part->write(i18n("Account")); m_part->write("").arg(colWidth)); m_part->write(i18n("%1 days").arg(i*m_forecast.accountsCycle() + beginDay)); m_part->write("
") + link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "").arg(colWidth)); m_part->write(QString("%1
%1
%1
"); } } const QString KHomeView::link(const QString& view, const QString& query, const QString& _title) const { QString titlePart; QString title(_title); if(!title.isEmpty()) titlePart = QString(" title=\"%1\"").arg(title.replace(" ", " ")); return QString("").arg(view, query, titlePart); } const QString KHomeView::linkend(void) const { return ""; } void KHomeView::slotOpenURL(const KURL &url, const KParts::URLArgs& /* args */) { QString protocol = url.protocol(); QString view = url.fileName(false); QString id = url.queryItem("id").data(); QString mode = url.queryItem("mode").data(); if ( protocol == "http" ) { KApplication::kApplication()->invokeBrowser(url.prettyURL()); } else if ( protocol == "mailto" ) { KApplication::kApplication()->invokeMailer(url); } else { if(view == VIEW_LEDGER) { emit ledgerSelected(id, QString()); } else if(view == VIEW_SCHEDULE) { if(mode == "enter") { emit scheduleSelected(id); KMainWindow* mw = dynamic_cast(qApp->mainWidget()); Q_CHECK_PTR(mw); QTimer::singleShot(0, mw->actionCollection()->action("schedule_enter"), SLOT(activate())); } else if(mode == "edit") { emit scheduleSelected(id); KMainWindow* mw = dynamic_cast(qApp->mainWidget()); Q_CHECK_PTR(mw); QTimer::singleShot(0, mw->actionCollection()->action("schedule_edit"), SLOT(activate())); } else if(mode == "skip") { emit scheduleSelected(id); KMainWindow* mw = dynamic_cast(qApp->mainWidget()); Q_CHECK_PTR(mw); QTimer::singleShot(0, mw->actionCollection()->action("schedule_skip"), SLOT(activate())); } else if(mode == "full") { m_showAllSchedules = true; loadView(); } else if(mode == "reduced") { m_showAllSchedules = false; loadView(); } } else if(view == VIEW_REPORTS) { emit reportSelected(id); } else if(view == VIEW_WELCOME) { KMainWindow* mw = dynamic_cast(qApp->mainWidget()); Q_CHECK_PTR(mw); if ( mode == "whatsnew" ) { QString fname = KMyMoneyUtils::findResource("appdata",QString("html/whats_new%1.html")); if(!fname.isEmpty()) m_part->openURL(fname); } else m_part->openURL(m_filename); } else if(view == "action") { KMainWindow* mw = dynamic_cast(qApp->mainWidget()); Q_CHECK_PTR(mw); QTimer::singleShot(0, mw->actionCollection()->action( id ), SLOT(activate())); } else if(view == VIEW_HOME) { QValueList list; MyMoneyFile::instance()->accountList(list); if(list.count() == 0) { KMessageBox::information(this, i18n("Before KMyMoney can give you detailed information about your financial status, you need to create at least one account. Until then, KMyMoney shows the welcome page instead.")); } loadView(); } else { qDebug("Unknown view '%s' in KHomeView::slotOpenURL()", view.latin1()); } } } void KHomeView::showAssetsLiabilities(void) { QValueList accounts; QValueList::Iterator it; QMap nameAssetsIdx; QMap nameLiabilitiesIdx; MyMoneyMoney netAssets; MyMoneyMoney netLiabilities; QString fontStart, fontEnd; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); int i = 0; // get list of all accounts file->accountList(accounts); for(it = accounts.begin(); it != accounts.end();) { if(!(*it).isClosed()) { switch((*it).accountType()) { // group all assets into one list but make sure that investment accounts always show up case MyMoneyAccount::Investment: d->addNameIndex(nameAssetsIdx, *it); break; case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::Asset: case MyMoneyAccount::AssetLoan: // list account if it's the last in the hierarchy or has transactions in it if((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { d->addNameIndex(nameAssetsIdx, *it); } break; // group the liabilities into the other case MyMoneyAccount::CreditCard: case MyMoneyAccount::Liability: case MyMoneyAccount::Loan: // list account if it's the last in the hierarchy or has transactions in it if((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { d->addNameIndex(nameLiabilitiesIdx, *it); } break; default: break; } } ++it; } //only do it if we have assets or liabilities account if(nameAssetsIdx.count() > 0 || nameLiabilitiesIdx.count() > 0) { //print header m_part->write("
" + i18n("Assets and Liabilities Summary") + "
\n
 
\n"); m_part->write(""); //column titles m_part->write(""); m_part->write(""); //intermediate row to separate both columns m_part->write(""); m_part->write(""); m_part->write(""); //get asset and liability accounts QMap::const_iterator asset_it = nameAssetsIdx.begin(); QMap::const_iterator liabilities_it = nameLiabilitiesIdx.begin(); for(; asset_it != nameAssetsIdx.end() || liabilities_it != nameLiabilitiesIdx.end();) { m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); //write an asset account if we still have any if(asset_it != nameAssetsIdx.end()) { MyMoneyMoney value; //investment accounts consolidate the balance of its subaccounts if( (*asset_it).accountType() == MyMoneyAccount::Investment) { value = investmentBalance(*asset_it); } else { value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate()); } //calculate balance for foreign currency accounts if((*asset_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*asset_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netAssets += baseValue; } else { netAssets += value; } //show the account without minimum balance showAccountEntry(*asset_it, value, MyMoneyMoney(), false); ++asset_it; } else { //write a white space if we don't m_part->write(""); } //leave the intermediate column empty m_part->write(""); //write a liability account if(liabilities_it != nameLiabilitiesIdx.end()) { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate()); //calculate balance if foreign currency if((*liabilities_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*liabilities_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netLiabilities += baseValue; } else { netLiabilities += value; } //show the account without minimum balance showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false); ++liabilities_it; } else { //leave the space empty if we run out of liabilities m_part->write(""); } m_part->write(""); } //calculate net worth MyMoneyMoney netWorth = netAssets+netLiabilities; //format assets, liabilities and net worth QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountAssets.replace(" "," "); amountLiabilities.replace(" "," "); amountNetWorth.replace(" "," "); m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); //print total for assets m_part->write(QString("").arg(i18n("Total Assets")).arg(showColoredAmount(amountAssets, netAssets.isNegative()))); //leave the intermediate column empty m_part->write(""); //print total liabilities m_part->write(QString("").arg(i18n("Total Liabilities")).arg(showColoredAmount(amountLiabilities, netLiabilities.isNegative()))); m_part->write(""); //print net worth m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); m_part->write(""); m_part->write(QString("").arg(i18n("Net Worth")).arg(showColoredAmount(amountNetWorth, netWorth.isNegative() ))); m_part->write(""); m_part->write("
"); m_part->write(i18n("Asset Accounts")); m_part->write(""); m_part->write(i18n("Current Balance")); m_part->write(""); m_part->write(i18n("Liability Accounts")); m_part->write(""); m_part->write(i18n("Current Balance")); m_part->write("
%1%2%1%2
%1%2
"); m_part->write("
"); } } void KHomeView::showBudget(void) { MyMoneyFile* file = MyMoneyFile::instance(); if ( file->countBudgets() ) { int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); int i = 0; //config report just like "Monthly Budgeted vs Actual MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, MyMoneyTransactionFilter::currentMonth, MyMoneyReport::eDetailAll, i18n("Monthly Budgeted vs. Actual"), i18n("Generated Report")); reportCfg.setBudget("Any",true); reports::PivotTable table(reportCfg); PivotGrid grid = table.grid(); //div header m_part->write("
" + i18n("Budget") + "
\n
 
\n"); //display budget summary m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(QString("")); MyMoneyMoney totalBudgetValue = grid.m_total[eBudget].m_total; MyMoneyMoney totalActualValue = grid.m_total[eActual].m_total; MyMoneyMoney totalBudgetDiffValue = grid.m_total[eBudgetDiff].m_total; QString totalBudgetAmount = totalBudgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalActualAmount = totalActualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalBudgetDiffAmount = totalBudgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); m_part->write(QString("").arg(showColoredAmount(totalBudgetAmount, totalBudgetValue.isNegative()))); m_part->write(QString("").arg(showColoredAmount(totalActualAmount, totalActualValue.isNegative()))); m_part->write(QString("").arg(showColoredAmount(totalBudgetDiffAmount, totalBudgetDiffValue.isNegative()))); m_part->write(""); m_part->write("
"); m_part->write(i18n("Current Month Summary")); m_part->write("
"); m_part->write(i18n("Budgeted")); m_part->write(""); m_part->write(i18n("Actual")); m_part->write(""); m_part->write(i18n("Difference")); m_part->write("
%1%1%1
"); //budget overrun m_part->write("
 
\n"); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); PivotGrid::iterator it_outergroup = grid.begin(); while ( it_outergroup != grid.end() ) { i = 0; PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while ( it_innergroup != (*it_outergroup).end() ) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while ( it_row != (*it_innergroup).end() ) { //column number is 1 because the report includes only current month if(it_row.data()[eBudgetDiff][1].isNegative()) { //get report account to get the name later ReportAccount rowname = it_row.key(); //write the outergroup if it is the first row of outergroup being shown if(i == 0) { m_part->write(""); m_part->write(QString("").arg(KMyMoneyUtils::accountTypeToString( rowname.accountType()))); m_part->write(""); } m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); //get values from grid MyMoneyMoney actualValue = it_row.data()[eActual][1]; MyMoneyMoney budgetValue = it_row.data()[eBudget][1]; MyMoneyMoney budgetDiffValue = it_row.data()[eBudgetDiff][1]; //format amounts QString actualAmount = actualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetAmount = budgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetDiffAmount = budgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); //account name m_part->write(QString(""); //show amounts m_part->write(QString("").arg(showColoredAmount(budgetAmount, budgetValue.isNegative()))); m_part->write(QString("").arg(showColoredAmount(actualAmount, actualValue.isNegative()))); m_part->write(QString("").arg(showColoredAmount(budgetDiffAmount, budgetDiffValue.isNegative()))); m_part->write(""); } ++it_row; } ++it_innergroup; } ++it_outergroup; } //if no negative differences are found, then inform that if(i == 0) { m_part->write(QString("").arg(i++ & 0x01 ? "even" : "odd")); m_part->write(QString("").arg(i18n("No Budget Categories have been overrun"))); m_part->write(""); } m_part->write("
"); m_part->write(i18n("Budget Overruns")); m_part->write("
"); m_part->write(i18n("Account")); m_part->write(""); m_part->write(i18n("Budgeted")); m_part->write(""); m_part->write(i18n("Actual")); m_part->write(""); m_part->write(i18n("Difference")); m_part->write("
%1
") + link(VIEW_LEDGER, QString("?id=%1").arg(rowname.id())) + rowname.name() + linkend() + "%1%1%1
%1
"); } } QString KHomeView::showColoredAmount(const QString& amount, bool isNegative) { if(isNegative) { //if negative, get the settings for negative numbers return QString("%2").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name(), amount); } //if positive, return the same string return amount; } void KHomeView::doForecast(void) { //clear m_accountList because forecast is about to changed m_accountList.clear(); //reinitialize the object m_forecast = MyMoneyForecast(); //If forecastDays lower than accountsCycle, adjust to the first cycle if(m_forecast.accountsCycle() > m_forecast.forecastDays()) m_forecast.setForecastDays(m_forecast.accountsCycle()); //Get all accounts of the right type to calculate forecast m_forecast.doForecast(); } MyMoneyMoney KHomeView::forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate) { //if paymentDate before or equal to currentDate set it to current date plus 1 //so we get to accumulate forecast balance correctly if(paymentDate <= QDate::currentDate()) paymentDate = QDate::currentDate().addDays(1); //check if the account is already there if(m_accountList.find(acc.id()) == m_accountList.end() || m_accountList[acc.id()].find(paymentDate) == m_accountList[acc.id()].end()) { if(paymentDate == QDate::currentDate()) { m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate); } else { m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate.addDays(-1)); } } m_accountList[acc.id()][paymentDate] = m_accountList[acc.id()][paymentDate] + payment; return m_accountList[acc.id()][paymentDate]; } void KHomeView::showCashFlowSummary() { MyMoneyTransactionFilter filter; MyMoneyMoney incomeValue; MyMoneyMoney expenseValue; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); //set start and end of month dates QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1); QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth()); //Add total income and expenses for this month //get transactions for current month filter.setDateFilter(startOfMonth, endOfMonth); filter.setReportAllSplits(false); QValueList transactions = file->transactionList(filter); //if no transaction then skip and print total in zero if(transactions.size() > 0) { QValueList::const_iterator it_transaction; //get all transactions for this month for(it_transaction = transactions.begin(); it_transaction != transactions.end(); ++it_transaction ) { //get the splits for each transaction const QValueList& splits = (*it_transaction).splits(); QValueList::const_iterator it_split; for(it_split = splits.begin(); it_split != splits.end(); ++it_split) { if(!(*it_split).shares().isZero()) { ReportAccount repSplitAcc = ReportAccount((*it_split).accountId()); //only add if it is an income or expense if(repSplitAcc.isIncomeExpense()) { MyMoneyMoney value; //convert to base currency if necessary if(repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice((*it_transaction).postDate()); value = ((*it_split).shares() * MyMoneyMoney(-1, 1)) * curPrice; value = value.convert(10000); } else { value = ((*it_split).shares() * MyMoneyMoney(-1, 1)); } //store depending on account type if(repSplitAcc.accountType() == MyMoneyAccount::Income) { incomeValue += value; } else { expenseValue += value; } } } } } } //format income and expenses QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountIncome.replace(" "," "); amountExpense.replace(" "," "); //calculate schedules //Add all schedules for this month MyMoneyMoney scheduledIncome; MyMoneyMoney scheduledExpense; MyMoneyMoney scheduledLiquidTransfer; MyMoneyMoney scheduledOtherTransfer; //get overdues and schedules until the end of this month QValueList schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), endOfMonth); //Remove the finished schedules QValueList::Iterator finished_it; for (finished_it=schedule.begin(); finished_it!=schedule.end();) { if ((*finished_it).isFinished()) { finished_it = schedule.remove(finished_it); continue; } ++finished_it; } //add income and expenses QValueList::Iterator sched_it; for (sched_it=schedule.begin(); sched_it!=schedule.end();) { QDate nextDate = (*sched_it).nextDueDate(); int cnt = 0; while(nextDate.isValid() && nextDate <= endOfMonth) { ++cnt; nextDate = (*sched_it).nextPayment(nextDate); // for single occurence nextDate will not change, so we // better get out of here. if((*sched_it).occurence() == MyMoneySchedule::OCCUR_ONCE) break; } MyMoneyAccount acc = (*sched_it).account(); if(acc.id()) { MyMoneyTransaction transaction = (*sched_it).transaction(); // only show the entry, if it is still active MyMoneySplit sp = transaction.splitByAccount(acc.id(), true); // take care of the autoCalc stuff if((*sched_it).type() == MyMoneySchedule::TYPE_LOANPAYMENT) { QDate nextDate = (*sched_it).nextPayment((*sched_it).lastPayment()); //make sure we have all 'starting balances' so that the autocalc works QValueList::const_iterator it_s; QMap balanceMap; for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s ) { MyMoneyAccount acc = file->account((*it_s).accountId()); // collect all overdues on the first day QDate schedDate = nextDate; if(QDate::currentDate() >= nextDate) schedDate = QDate::currentDate().addDays(1); balanceMap[acc.id()] += file->balance(acc.id()); } KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap); } //go through the splits and assign to liquid or other transfers const QValueList splits = transaction.splits(); QValueList::const_iterator split_it; for (split_it = splits.begin(); split_it != splits.end(); ++split_it) { if( (*split_it).accountId() != acc.id() ) { ReportAccount repSplitAcc = ReportAccount((*split_it).accountId()); //get the shares and multiply by the quantity of occurences in the period MyMoneyMoney value = (*split_it).shares() * cnt; //convert to foreign currency if needed if(repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice(QDate::currentDate()); value = value * curPrice; value = value.convert(10000); } if(( repSplitAcc.isLiquidLiability() || repSplitAcc.isLiquidAsset() ) && acc.accountGroup() != repSplitAcc.accountGroup()) { scheduledLiquidTransfer += value; } else if(repSplitAcc.isAssetLiability() && !repSplitAcc.isLiquidLiability() && !repSplitAcc.isLiquidAsset() ) { scheduledOtherTransfer += value; } else if(repSplitAcc.isIncomeExpense()) { //income and expenses are stored as negative values if(repSplitAcc.accountType() == MyMoneyAccount::Income) scheduledIncome -= value; if(repSplitAcc.accountType() == MyMoneyAccount::Expense) scheduledExpense -= value; } } } } ++sched_it; } //format the currency strings QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountScheduledIncome.replace(" "," "); amountScheduledExpense.replace(" "," "); amountScheduledLiquidTransfer.replace(" "," "); amountScheduledOtherTransfer.replace(" "," "); //get liquid assets and liabilities QValueList accounts; QValueList::const_iterator account_it; MyMoneyMoney liquidAssets; MyMoneyMoney liquidLiabilities; // get list of all accounts file->accountList(accounts); for(account_it = accounts.begin(); account_it != accounts.end();) { if(!(*account_it).isClosed()) { switch((*account_it).accountType()) { //group all assets into one list case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: { MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance for foreign currency accounts if((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidAssets += baseValue; liquidAssets = liquidAssets.convert(10000); } else { liquidAssets += value; } break; } //group the liabilities into the other case MyMoneyAccount::CreditCard: { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance if foreign currency if((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidLiabilities += baseValue; liquidLiabilities = liquidLiabilities.convert(10000); } else { liquidLiabilities += value; } break; } default: break; } } ++account_it; } //calculate net worth MyMoneyMoney liquidWorth = liquidAssets+liquidLiabilities; //format assets, liabilities and net worth QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountLiquidAssets.replace(" "," "); amountLiquidLiabilities.replace(" "," "); amountLiquidWorth.replace(" "," "); //show the summary m_part->write("
" + i18n("Cash Flow Summary") + "
\n
 
\n"); //print header m_part->write(""); //income and expense title m_part->write(""); m_part->write(""); //column titles m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); //add row with banding m_part->write(QString("")); //print current income m_part->write(QString("").arg(showColoredAmount(amountIncome, incomeValue.isNegative()))); //print the scheduled income m_part->write(QString("").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative()))); //print current expenses m_part->write(QString("").arg(showColoredAmount(amountExpense, expenseValue.isNegative()))); //print the scheduled expenses m_part->write(QString("").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative()))); m_part->write(""); m_part->write("
"); m_part->write(i18n("Income and Expenses of Current Month")); m_part->write("
"); m_part->write(i18n("Income")); m_part->write(""); m_part->write(i18n("Scheduled Income")); m_part->write(""); m_part->write(i18n("Expenses")); m_part->write(""); m_part->write(i18n("Scheduled Expenses")); m_part->write("
%2%2%2%2
"); //print header of assets and liabilities m_part->write("
 
\n"); m_part->write(""); //assets and liabilities title m_part->write(""); m_part->write(""); //column titles m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); //add row with banding m_part->write(QString("")); //print current liquid assets m_part->write(QString("").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative()))); //print the scheduled transfers m_part->write(QString("").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative()))); //print current liabilities m_part->write(QString("").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative()))); //print the scheduled transfers m_part->write(QString("").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative()))); m_part->write(""); m_part->write("
"); m_part->write(i18n("Liquid Assets and Liabilities")); m_part->write("
"); m_part->write(i18n("Liquid Assets")); m_part->write(""); m_part->write(i18n("Transfers to Liquid Liabilities")); m_part->write(""); m_part->write(i18n("Liquid Liabilities")); m_part->write(""); m_part->write(i18n("Other Transfers")); m_part->write("
%2%2%2%2
"); //final conclusion MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense; MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer; MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer; QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountProfit.replace(" "," "); amountExpectedAsset.replace(" "," "); amountExpectedLiabilities.replace(" "," "); //print header of cash flow status m_part->write("
 
\n"); m_part->write(""); //income and expense title m_part->write(""); m_part->write(""); //column titles m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); m_part->write(""); //add row with banding m_part->write(QString("")); m_part->write(""); //print expected assets m_part->write(QString("").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative()))); //print expected liabilities m_part->write(QString("").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative()))); //print expected profit m_part->write(QString("").arg(showColoredAmount(amountProfit, profitValue.isNegative()))); m_part->write(""); m_part->write("
"); m_part->write(i18n("Cash Flow Status")); m_part->write("
 "); m_part->write(i18n("Expected Liquid Assets")); m_part->write(""); m_part->write(i18n("Expected Liquid Liabilities")); m_part->write(""); m_part->write(i18n("Expected Profit/Loss")); m_part->write("
 %2%2%2
"); m_part->write("
"); } // Make sure, that these definitions are only used within this file // this does not seem to be necessary, but when building RPMs the // build option 'final' is used and all CPP files are concatenated. // So it could well be, that in another CPP file these definitions // are also used. #undef VIEW_LEDGER #undef VIEW_SCHEDULE #undef VIEW_WELCOME #undef VIEW_HOME #undef VIEW_REPORTS #include "khomeview.moc"