File indexing completed on 2024-05-19 05:08:17
0001 /* 0002 SPDX-FileCopyrightText: 2000-2002 Michael Edwardes <mte@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2000-2002 Javier Campos Morales <javi_c@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2000-2002 Felix Rodriguez <frodriguez@users.sourceforge.net> 0005 SPDX-FileCopyrightText: 2000-2002 John C <thetacoturtle@users.sourceforge.net> 0006 SPDX-FileCopyrightText: 2000-2002 Thomas Baumgart <ipwizard@users.sourceforge.net> 0007 SPDX-FileCopyrightText: 2000-2002 Kevin Tambascio <ktambascio@users.sourceforge.net> 0008 SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0009 SPDX-FileCopyrightText: 2021 Dawid Wróbel <me@dawidwrobel.com> 0010 SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 0013 #ifndef KHOMEVIEW_P_H 0014 #define KHOMEVIEW_P_H 0015 0016 #include "khomeview.h" 0017 0018 #include <config-kmymoney.h> 0019 0020 // ---------------------------------------------------------------------------- 0021 // QT Includes 0022 0023 #include <QBuffer> 0024 #include <QDesktopServices> 0025 #include <QElapsedTimer> 0026 #include <QList> 0027 #include <QPixmap> 0028 #include <QPrintDialog> 0029 #include <QPrinter> 0030 #include <QScrollBar> 0031 #include <QStandardPaths> 0032 #include <QTimer> 0033 #include <QUrlQuery> 0034 #include <QVBoxLayout> 0035 #include <QWheelEvent> 0036 0037 #include <cmath> 0038 0039 // ---------------------------------------------------------------------------- 0040 // KDE Includes 0041 0042 #include <KLocalizedString> 0043 #include <KXmlGuiWindow> 0044 #include <KActionCollection> 0045 #include <KMessageBox> 0046 0047 // ---------------------------------------------------------------------------- 0048 // Project Includes 0049 0050 #include "icons.h" 0051 #include "kmmtextbrowser.h" 0052 #include "kmymoneyplugin.h" 0053 #include "kmymoneysettings.h" 0054 #include "kmymoneyutils.h" 0055 #include "kmymoneyviewbase_p.h" 0056 #include "kwelcomepage.h" 0057 #include "menuenums.h" 0058 #include "mymoneyaccount.h" 0059 #include "mymoneyenums.h" 0060 #include "mymoneyexception.h" 0061 #include "mymoneyfile.h" 0062 #include "mymoneyforecast.h" 0063 #include "mymoneymoney.h" 0064 #include "mymoneyprice.h" 0065 #include "mymoneyreport.h" 0066 #include "mymoneyschedule.h" 0067 #include "mymoneysecurity.h" 0068 #include "mymoneysplit.h" 0069 #include "mymoneytransaction.h" 0070 #include "mymoneyutils.h" 0071 #include "plugins/views/reports/reportsviewenums.h" 0072 0073 #define VIEW_LEDGER "ledger" 0074 #define VIEW_SCHEDULE "schedule" 0075 #define VIEW_WELCOME "welcome" 0076 #define VIEW_HOME "home" 0077 #define VIEW_REPORTS "reports" 0078 0079 using namespace Icons; 0080 using namespace eMyMoney; 0081 0082 /** 0083 * @brief Converts a QPixmap to an data URI scheme 0084 * 0085 * According to RFC 2397 0086 * 0087 * @param pixmap Source to convert 0088 * @return full data URI 0089 */ 0090 QString QPixmapToDataUri(const QPixmap& pixmap) 0091 { 0092 QImage image(pixmap.toImage()); 0093 QByteArray byteArray; 0094 QBuffer buffer(&byteArray); 0095 buffer.open(QIODevice::WriteOnly); 0096 image.save(&buffer, "PNG"); // writes the image in PNG format inside the buffer 0097 return QLatin1String("data:image/png;base64,") + QString(byteArray.toBase64()); 0098 } 0099 0100 bool accountNameLess(const MyMoneyAccount &acc1, const MyMoneyAccount &acc2) 0101 { 0102 return acc1.name().localeAwareCompare(acc2.name()) < 0; 0103 } 0104 0105 class KHomeViewPrivate : public KMyMoneyViewBasePrivate 0106 { 0107 Q_DECLARE_PUBLIC(KHomeView) 0108 0109 public: 0110 explicit KHomeViewPrivate(KHomeView* qq) 0111 : KMyMoneyViewBasePrivate(qq) 0112 , m_view(nullptr) 0113 , m_showAllSchedules(false) 0114 , m_needLoad(true) 0115 , m_skipRefresh(false) 0116 , m_netWorthGraphLastValidSize(400, 300) 0117 , m_scrollBarPos(0) 0118 , m_fileOpen(false) 0119 , m_adjustedIconSize(0) 0120 , m_devRatio(1.0) 0121 { 0122 } 0123 0124 ~KHomeViewPrivate() { 0125 Q_Q(KHomeView); 0126 // if user wants to remember the font size, store it here 0127 if (KMyMoneySettings::rememberZoomFactor() && m_view) { 0128 // zoom factor 0129 KMyMoneySettings::setZoomFactor(m_view->font().pointSizeF() / q->font().pointSizeF()); 0130 KMyMoneySettings::self()->save(); 0131 } 0132 } 0133 0134 /** 0135 * Definition of bitmap used as argument for showAccounts(). 0136 */ 0137 enum paymentTypeE { 0138 Preferred = 1, ///< show preferred accounts 0139 Payment = 2, ///< show payment accounts 0140 }; 0141 0142 void init() 0143 { 0144 Q_Q(KHomeView); 0145 m_needLoad = false; 0146 0147 auto vbox = new QVBoxLayout(q); 0148 q->setLayout(vbox); 0149 vbox->setSpacing(6); 0150 vbox->setContentsMargins(0, 0, 0, 0); 0151 0152 m_view = new KMMTextBrowser(); 0153 m_view->setOpenLinks(false); 0154 m_view->installEventFilter(q); 0155 0156 vbox->addWidget(m_view); 0157 0158 q->connect(m_view, &KMMTextBrowser::anchorClicked, q, &KHomeView::slotOpenUrl); 0159 0160 q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KHomeView::refresh); 0161 0162 m_resizeRefreshTimer.setSingleShot(true); 0163 q->connect(&m_resizeRefreshTimer, &QTimer::timeout, q, &KHomeView::refresh); 0164 } 0165 0166 /** 0167 * Print an account and its balance and limit 0168 */ 0169 void showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal) 0170 { 0171 MyMoneyFile* file = MyMoneyFile::instance(); 0172 QString tmp; 0173 MyMoneySecurity currency = file->currency(acc.currencyId()); 0174 QString amount; 0175 QString amountToMinBal; 0176 0177 //format amounts 0178 amount = MyMoneyUtils::formatMoney(value, acc, currency); 0179 amount.replace(QChar(' '), " "); 0180 if (showMinBal) { 0181 amountToMinBal = MyMoneyUtils::formatMoney(valueToMinBal, acc, currency); 0182 amountToMinBal.replace(QChar(' '), " "); 0183 } 0184 0185 QString cellStatus, pathOK, pathTODO, pathNotOK; 0186 0187 if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { 0188 //show account's online-status 0189 pathOK = QPixmapToDataUri(Icons::get(Icon::DialogOKApply).pixmap(QSize(m_adjustedIconSize, m_adjustedIconSize))); 0190 pathTODO = QPixmapToDataUri(Icons::get(Icon::MailReceive).pixmap(QSize(m_adjustedIconSize, m_adjustedIconSize))); 0191 pathNotOK = QPixmapToDataUri(Icons::get(Icon::DialogCancel).pixmap(QSize(m_adjustedIconSize, m_adjustedIconSize))); 0192 0193 if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty()) 0194 cellStatus = '-'; 0195 else if (file->hasMatchingOnlineBalance(acc)) { 0196 if (file->hasNewerTransaction(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate))) 0197 cellStatus = QString("<img src=\"%1\" border=\"0\">").arg(pathTODO); 0198 else 0199 cellStatus = QString("<img src=\"%1\" border=\"0\">").arg(pathOK); 0200 } else 0201 cellStatus = QString("<img src=\"%1\" border=\"0\">").arg(pathNotOK); 0202 0203 tmp = QString("<td class=\"center nowrap\">%1</td>").arg(cellStatus); 0204 } 0205 0206 tmp += QString("<td>") + link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())); 0207 if (acc.isClosed()) { 0208 tmp += QLatin1String("<s>"); 0209 } 0210 tmp += acc.name().replace("<", "<").replace(">", ">"); 0211 if (acc.isClosed()) { 0212 tmp += QLatin1String("</s>"); 0213 } 0214 tmp += linkend() + "</td>"; 0215 0216 int countNotMarked = 0, countCleared = 0, countNotReconciled = 0; 0217 QString countStr; 0218 0219 if (KMyMoneySettings::showCountOfUnmarkedTransactions() || KMyMoneySettings::showCountOfNotReconciledTransactions()) 0220 countNotMarked = m_transactionStats[acc.id()][(int)Split::State::NotReconciled]; 0221 0222 if (KMyMoneySettings::showCountOfClearedTransactions() || KMyMoneySettings::showCountOfNotReconciledTransactions()) 0223 countCleared = m_transactionStats[acc.id()][(int)Split::State::Cleared]; 0224 0225 if (KMyMoneySettings::showCountOfNotReconciledTransactions()) 0226 countNotReconciled = countNotMarked + countCleared; 0227 0228 if (KMyMoneySettings::showCountOfUnmarkedTransactions()) { 0229 if (countNotMarked) 0230 countStr = QString("%1").arg(countNotMarked); 0231 else 0232 countStr = '-'; 0233 tmp += QString("<td class=\"center nowrap\">%1</td>").arg(countStr); 0234 } 0235 0236 if (KMyMoneySettings::showCountOfClearedTransactions()) { 0237 if (countCleared) 0238 countStr = QString("%1").arg(countCleared); 0239 else 0240 countStr = '-'; 0241 tmp += QString("<td class=\"center nowrap\">%1</td>").arg(countStr); 0242 } 0243 0244 if (KMyMoneySettings::showCountOfNotReconciledTransactions()) { 0245 if (countNotReconciled) 0246 countStr = QString("%1").arg(countNotReconciled); 0247 else 0248 countStr = '-'; 0249 tmp += QString("<td class=\"center nowrap\">%1</td>").arg(countStr); 0250 } 0251 0252 if (KMyMoneySettings::showDateOfLastReconciliation()) { 0253 const auto lastReconciliationDate = MyMoneyUtils::formatDate(acc.lastReconciliationDate()).replace(QChar(' '), " "); 0254 tmp += QString("<td>%1</d>").arg(lastReconciliationDate); 0255 } 0256 0257 //show account balance 0258 tmp += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(amount, value.isNegative())); 0259 0260 //show minimum balance column if requested 0261 if (showMinBal) { 0262 //if it is an investment, show minimum balance empty 0263 if (acc.accountType() == Account::Type::Investment) { 0264 tmp += QString("<td class=\"right nowrap\"> </td>"); 0265 } else { 0266 //show minimum balance entry 0267 tmp += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative())); 0268 } 0269 } 0270 // qDebug("accountEntry = '%s'", tmp.toLatin1()); 0271 m_html += tmp; 0272 } 0273 0274 void showAccountEntry(const MyMoneyAccount& acc) 0275 { 0276 const auto file = MyMoneyFile::instance(); 0277 MyMoneyMoney value; 0278 0279 bool showLimit = KMyMoneySettings::showLimitInfo(); 0280 0281 if (acc.accountType() == Account::Type::Investment) { 0282 //investment accounts show the balances of all its subaccounts 0283 value = investmentBalance(acc); 0284 0285 //investment accounts have no minimum balance 0286 showAccountEntry(acc, value, MyMoneyMoney(), showLimit); 0287 } else { 0288 //get balance for normal accounts 0289 value = file->balance(acc.id(), QDate::currentDate()); 0290 if (acc.currencyId() != file->baseCurrency().id()) { 0291 const auto curPrice = file->price(acc.tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); 0292 const auto curRate = curPrice.rate(file->baseCurrency().id()); 0293 auto baseValue = value * curRate; 0294 baseValue = baseValue.convert(file->baseCurrency().smallestAccountFraction()); 0295 m_total += baseValue; 0296 } else { 0297 m_total += value; 0298 } 0299 //if credit card or checkings account, show maximum credit 0300 if (acc.accountType() == Account::Type::CreditCard || 0301 acc.accountType() == Account::Type::Checkings) { 0302 QString maximumCredit = acc.value("maxCreditAbsolute"); 0303 if (maximumCredit.isEmpty()) { 0304 maximumCredit = acc.value("minBalanceAbsolute"); 0305 } 0306 MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit); 0307 showAccountEntry(acc, value, value - maxCredit, showLimit); 0308 } else { 0309 //otherwise use minimum balance 0310 QString minimumBalance = acc.value("minBalanceAbsolute"); 0311 MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); 0312 showAccountEntry(acc, value, value - minBalance, showLimit); 0313 } 0314 } 0315 } 0316 0317 /** 0318 * @param acc the investment account 0319 * @return the balance in the currency of the investment account 0320 */ 0321 MyMoneyMoney investmentBalance(const MyMoneyAccount& acc) 0322 { 0323 auto file = MyMoneyFile::instance(); 0324 auto value = file->balance(acc.id(), QDate::currentDate()); 0325 const auto subAccountList = acc.accountList(); 0326 for (const auto& accountID : qAsConst(subAccountList)) { 0327 auto stock = file->account(accountID); 0328 if (!stock.isClosed()) { 0329 try { 0330 MyMoneyMoney val; 0331 MyMoneyMoney balance = file->balance(stock.id(), QDate::currentDate()); 0332 MyMoneySecurity security = file->security(stock.currencyId()); 0333 const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency()); 0334 val = (balance * price.rate(security.tradingCurrency())).convertPrecision(security.pricePrecision()); 0335 // adjust value of security to the currency of the account 0336 MyMoneySecurity accountCurrency = file->currency(acc.currencyId()); 0337 val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id()); 0338 val = val.convert(acc.fraction()); 0339 value += val; 0340 } catch (const MyMoneyException &e) { 0341 qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what()))); 0342 } 0343 } 0344 } 0345 return value; 0346 } 0347 0348 /** 0349 * Print text in the color set for negative numbers, if @p amount is negative 0350 * abd @p isNegative is true 0351 */ 0352 QString showColoredAmount(const QString& amount, bool isNegative) 0353 { 0354 if (isNegative) { 0355 //if negative, get the settings for negative numbers 0356 return QString("<font color=\"%1\">%2</font>").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name(), amount); 0357 } 0358 0359 //if positive, return the same string 0360 return amount; 0361 } 0362 0363 /** 0364 * Run the forecast 0365 */ 0366 void doForecast() 0367 { 0368 //clear m_accountList because forecast is about to changed 0369 m_accountList.clear(); 0370 0371 //reinitialize the object 0372 m_forecast = KMyMoneyUtils::forecast(); 0373 0374 //If forecastDays lower than accountsCycle, adjust to the first cycle 0375 if (m_forecast.accountsCycle() > m_forecast.forecastDays()) 0376 m_forecast.setForecastDays(m_forecast.accountsCycle()); 0377 0378 //Get all accounts of the right type to calculate forecast 0379 m_forecast.doForecast(); 0380 } 0381 0382 /** 0383 * Calculate the forecast balance after a payment has been made 0384 */ 0385 MyMoneyMoney forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate) 0386 { 0387 //if paymentDate before or equal to currentDate set it to current date plus 1 0388 //so we get to accumulate forecast balance correctly 0389 if (paymentDate <= QDate::currentDate()) 0390 paymentDate = QDate::currentDate().addDays(1); 0391 0392 //check if the account is already there 0393 if (m_accountList.find(acc.id()) == m_accountList.end() 0394 || m_accountList[acc.id()].find(paymentDate) == m_accountList[acc.id()].end()) { 0395 if (paymentDate == QDate::currentDate()) { 0396 m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate); 0397 } else { 0398 m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate.addDays(-1)); 0399 } 0400 } 0401 m_accountList[acc.id()][paymentDate] = m_accountList[acc.id()][paymentDate] + payment; 0402 return m_accountList[acc.id()][paymentDate]; 0403 } 0404 0405 /** 0406 * Trigger a reload if no other resize event is received within 0407 * 100ms but only if the size does not toggle. It happens, that 0408 * two resize events happen short after another with the 0409 * following sizes (examples) 0410 * 0411 * event oldSize newSize 0412 * ----------------------- 0413 * 1 1127,777 1127,751 0414 * 2 1127,751 1127,777 0415 * 0416 * when re-entering the home view. repaintAfterResize() 0417 * stops the m_resizeRefreshTimer in this case. 0418 */ 0419 void repaintAfterResize(const QSize& oldSize, const QSize& newSize) 0420 { 0421 if (!m_resizeRefreshTimer.isActive()) { 0422 m_startSize = oldSize; 0423 m_resizeRefreshTimer.start(100); 0424 } else { 0425 if (m_startSize == newSize) { 0426 m_resizeRefreshTimer.stop(); 0427 } 0428 } 0429 } 0430 0431 void loadView() 0432 { 0433 Q_Q(KHomeView); 0434 0435 const auto stockPointSize = q->font().pointSizeF(); 0436 const auto currentPointSize = m_view->font().pointSizeF(); 0437 const auto pointSizeDelta = (stockPointSize * KMyMoneySettings::zoomFactor()) - currentPointSize; 0438 m_view->zoomIn(pointSizeDelta); 0439 0440 m_view->setHtml(KWelcomePage::welcomePage()); 0441 0442 if (!m_fileOpen) 0443 return; 0444 0445 // preload transaction statistics 0446 m_transactionStats = MyMoneyFile::instance()->countTransactionsWithSpecificReconciliationState(); 0447 0448 // keep current location on page 0449 m_scrollBarPos = m_view->verticalScrollBar()->value(); 0450 0451 // clear the forecast flag so it will be reloaded 0452 m_forecast.setForecastDone(false); 0453 0454 QString header = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<html><head>\n"); 0455 0456 // inline the CSS 0457 header += "<style type=\"text/css\">\n"; 0458 header += KMyMoneyUtils::getStylesheet(); 0459 header += "</style>\n"; 0460 0461 header += "</head><body id=\"summaryview\">\n"; 0462 0463 QString footer = "</body></html>\n"; 0464 0465 m_html.clear(); 0466 m_html += header; 0467 0468 m_html += QString("<div class=\"gap\"> </div>"); 0469 0470 prepareIcons(); 0471 0472 QStringList settings = KMyMoneySettings::listOfItems(); 0473 0474 QStringList::ConstIterator it; 0475 0476 QElapsedTimer t; 0477 t.start(); 0478 for (it = settings.constBegin(); it != settings.constEnd(); ++it) { 0479 int option = (*it).toInt(); 0480 if (option > 0) { 0481 switch (option) { 0482 case 1: // payments 0483 showScheduledPayments(); 0484 break; 0485 0486 case 2: // preferred accounts 0487 showAccounts(Preferred, i18n("Preferred Accounts")); 0488 break; 0489 0490 case 3: // payment accounts 0491 // Check if preferred accounts are shown separately 0492 if (settings.contains("2")) { 0493 showAccounts(static_cast<paymentTypeE>(Payment | Preferred), i18n("Payment Accounts")); 0494 } else { 0495 showAccounts(Payment, i18n("Payment Accounts")); 0496 } 0497 break; 0498 case 4: // favorite reports 0499 showFavoriteReports(); 0500 break; 0501 case 5: // forecast 0502 showForecast(); 0503 break; 0504 case 6: // net worth graph over all accounts 0505 showNetWorthGraph(); 0506 break; 0507 case 7: // forecast (history) - currently unused 0508 break; 0509 case 8: // assets and liabilities 0510 showAssetsLiabilities(); 0511 break; 0512 case 9: // budget 0513 showBudget(); 0514 break; 0515 case 10: // cash flow summary 0516 showCashFlowSummary(); 0517 break; 0518 } 0519 m_html += "<div class=\"gap\"> </div>\n"; 0520 qDebug() << "Processed home view section" << option << "in" << t.restart() << "ms"; 0521 } 0522 } 0523 m_html += footer; 0524 0525 m_view->setHtml(m_html); 0526 0527 if (m_scrollBarPos) { 0528 QMetaObject::invokeMethod(q, "slotAdjustScrollPos", Qt::QueuedConnection); 0529 } 0530 } 0531 0532 void showNetWorthGraph() 0533 { 0534 Q_Q(KHomeView); 0535 0536 // Adjust the size 0537 QSize netWorthGraphSize = q->size(); 0538 netWorthGraphSize -= QSize(122, 30); 0539 netWorthGraphSize /= m_devRatio; 0540 m_netWorthGraphLastValidSize = netWorthGraphSize; 0541 0542 // print header 0543 m_html += 0544 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 0545 "<tr><td class=\"summaryheader\">" 0546 + i18n("Net Worth Forecast") + "</td></tr>"; 0547 0548 m_html += "<tr class=\"gap\"><td> \n</td></tr>"; 0549 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" ><tr>"; 0550 0551 if (const auto reportsPlugin = pPlugins.data.value(QStringLiteral("reportsview"), nullptr)) { 0552 const auto variantReport = reportsPlugin->requestData(QString(), eWidgetPlugin::WidgetType::NetWorthForecast); 0553 if (!variantReport.isNull()) { 0554 auto report = variantReport.value<QWidget *>(); 0555 report->resize(m_netWorthGraphLastValidSize); 0556 m_html += QString("<td align=center height=\"100%\"><img src=\"%1\" ALT=\"Networth\" border=\"0\"></td>").arg(QPixmapToDataUri(report->grab())); 0557 delete report; 0558 } 0559 } else { 0560 m_html += QString("<td><center>%1</center></td>").arg(i18n("Enable reports plugin to see this chart.")); 0561 } 0562 0563 m_html += "</tr></table></td></tr>"; 0564 m_html += "</table>"; 0565 } 0566 0567 void prepareIcons() 0568 { 0569 // calculate "enter" ans "skip" icon sizes 0570 auto fsize = m_view->fontMetrics().height(); 0571 // consider icon to be 75% of the label size 0572 auto isize = fsize * 3 / 4 + 1; 0573 0574 // check whether we have a device scaling enabled 0575 // if a device ratio is 2 (200% scale), we need to create a pixmap using half of the target size, 0576 // resulted pixmaps will be twice as large as provided in the QSize(...) 0577 auto ic = Icons::get(Icon::KeyEnter).pixmap(QSize(isize, isize)); 0578 m_devRatio = ic.devicePixelRatio(); 0579 if (m_devRatio > 1) 0580 isize = round(isize / m_devRatio); 0581 0582 pathEnterIcon = QPixmapToDataUri(Icons::get(Icon::KeyEnter).pixmap(QSize(isize, isize))); 0583 pathSkipIcon = QPixmapToDataUri(Icons::get(Icon::SkipForward).pixmap(QSize(isize, isize))); 0584 pathStatusHeader = QPixmapToDataUri(Icons::get(Icon::Download).pixmap(QSize(isize, isize))); 0585 0586 m_adjustedIconSize = isize; 0587 } 0588 0589 void showScheduledPayments() 0590 { 0591 MyMoneyFile* file = MyMoneyFile::instance(); 0592 QList<MyMoneySchedule> overdues; 0593 QList<MyMoneySchedule> schedule; 0594 0595 //if forecast has not been executed yet, do it. 0596 if (!m_forecast.isForecastDone()) 0597 doForecast(); 0598 0599 schedule = file->scheduleList(QString(), Schedule::Type::Any, 0600 Schedule::Occurrence::Any, 0601 Schedule::PaymentType::Any, 0602 QDate::currentDate(), 0603 QDate::currentDate().addMonths(1), false); 0604 overdues = file->scheduleList(QString(), Schedule::Type::Any, 0605 Schedule::Occurrence::Any, 0606 Schedule::PaymentType::Any, 0607 QDate(), QDate(), true); 0608 0609 if (schedule.empty() && overdues.empty()) 0610 return; 0611 0612 // HACK 0613 // Remove the finished schedules 0614 QList<MyMoneySchedule>::Iterator d_it; 0615 //regular schedules 0616 d_it = schedule.begin(); 0617 while (d_it != schedule.end()) { 0618 if ((*d_it).isFinished()) { 0619 d_it = schedule.erase(d_it); 0620 continue; 0621 } 0622 ++d_it; 0623 } 0624 //overdue schedules 0625 d_it = overdues.begin(); 0626 while (d_it != overdues.end()) { 0627 if ((*d_it).isFinished()) { 0628 d_it = overdues.erase(d_it); 0629 continue; 0630 } 0631 ++d_it; 0632 } 0633 0634 // print header 0635 m_html += 0636 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 0637 "<tr><td class=\"summaryheader\">" 0638 + i18n("Scheduled Payments") + "</td></tr>"; 0639 0640 if (!overdues.isEmpty()) { 0641 m_html += "<tr class=\"gap\"><td> \n</td></tr>"; 0642 0643 std::sort(overdues.begin(), overdues.end()); 0644 QList<MyMoneySchedule>::Iterator it; 0645 QList<MyMoneySchedule>::Iterator it_f; 0646 0647 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 0648 m_html += QString("<tr class=\"itemtitle negativetext\"><td colspan=\"7\">%1</td></tr>\n").arg(i18n("Overdue payments")); 0649 m_html += "<tr class=\"item\">"; 0650 m_html += "<td class=\"left\" width=\"10%\">"; 0651 m_html += i18n("Date"); 0652 m_html += "</td>"; 0653 m_html += "<td class=\"left\" width=\"2%\">"; 0654 m_html += i18n("Next"); 0655 m_html += "</td>"; 0656 m_html += "<td class=\"left\" width=\"2%\">"; 0657 m_html += i18n("Skip"); 0658 m_html += "</td>"; 0659 m_html += "<td class=\"left\" width=\"36%\">"; 0660 m_html += i18n("Schedule"); 0661 m_html += "</td>"; 0662 m_html += "<td class=\"left\" width=\"20%\">"; 0663 m_html += i18n("Account"); 0664 m_html += "</td>"; 0665 m_html += "<td class=\"right nowrap\" width=\"15%\">"; 0666 m_html += i18n("Amount"); 0667 m_html += "</td>"; 0668 m_html += "<td class=\"right nowrap\" width=\"15%\">"; 0669 m_html += i18n("Balance after"); 0670 m_html += "</td>"; 0671 m_html += "</tr>"; 0672 0673 int i = 0; 0674 for (it = overdues.begin(); it != overdues.end(); ++it) { 0675 // determine number of overdue payments 0676 int cnt = 0677 (*it).transactionsRemainingUntil(QDate::currentDate().addDays(-1)); 0678 0679 i = showPaymentEntry(*it, i, cnt); 0680 } 0681 m_html += "</table></td></tr>"; 0682 } 0683 0684 if (!schedule.isEmpty()) { 0685 std::sort(schedule.begin(), schedule.end()); 0686 0687 // Extract todays payments if any 0688 QList<MyMoneySchedule> todays; 0689 QList<MyMoneySchedule>::Iterator t_it; 0690 for (t_it = schedule.begin(); t_it != schedule.end();) { 0691 if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { 0692 todays.append(*t_it); 0693 (*t_it).setNextDueDate((*t_it).nextPayment(QDate::currentDate())); 0694 0695 // if adjustedNextDueDate is still currentDate then remove it from 0696 // scheduled payments 0697 if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { 0698 t_it = schedule.erase(t_it); 0699 continue; 0700 } 0701 } 0702 ++t_it; 0703 } 0704 0705 if (todays.count() > 0) { 0706 m_html += "<tr class=\"gap\"><td> \n</td></tr>"; 0707 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 0708 m_html += QString("<tr class=\"itemtitle\"><td class=\"left\" colspan=\"7\">%1</td></tr>\n").arg(i18n("Today's due payments")); 0709 m_html += "<tr class=\"item\">"; 0710 m_html += "<td class=\"left\" width=\"10%\">"; 0711 m_html += i18n("Date"); 0712 m_html += "</td>"; 0713 m_html += "<td class=\"left\" width=\"2%\">"; 0714 m_html += i18n("Next"); 0715 m_html += "</td>"; 0716 m_html += "<td class=\"left\" width=\"2%\">"; 0717 m_html += i18n("Skip"); 0718 m_html += "</td>"; 0719 m_html += "<td class=\"left\" width=\"36%\">"; 0720 m_html += i18n("Schedule"); 0721 m_html += "</td>"; 0722 m_html += "<td class=\"left\" width=\"20%\">"; 0723 m_html += i18n("Account"); 0724 m_html += "</td>"; 0725 m_html += "<td class=\"right nowrap\" width=\"15%\">"; 0726 m_html += i18n("Amount"); 0727 m_html += "</td>"; 0728 m_html += "<td class=\"right nowrap\" width=\"15%\">"; 0729 m_html += i18n("Balance after"); 0730 m_html += "</td>"; 0731 m_html += "</tr>"; 0732 0733 int i = 0; 0734 for (t_it = todays.begin(); t_it != todays.end(); ++t_it) { 0735 i = showPaymentEntry(*t_it, i); 0736 } 0737 m_html += "</table></td></tr>"; 0738 } 0739 0740 if (!schedule.isEmpty()) { 0741 m_html += "<tr class=\"gap\"><td> \n</td></tr>"; 0742 0743 QList<MyMoneySchedule>::Iterator it; 0744 0745 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 0746 m_html += QString("<tr class=\"itemtitle\"><td class=\"left\" colspan=\"7\">%1</td></tr>\n").arg(i18n("Future payments")); 0747 m_html += "<tr class=\"item\">"; 0748 m_html += "<td class=\"left\" width=\"10%\">"; 0749 m_html += i18n("Date"); 0750 m_html += "</td>"; 0751 m_html += "<td class=\"left\" width=\"2%\">"; 0752 m_html += i18n("Next"); 0753 m_html += "</td>"; 0754 m_html += "<td class=\"left\" width=\"2%\">"; 0755 m_html += i18n("Skip"); 0756 m_html += "</td>"; 0757 m_html += "<td class=\"left\" width=\"36%\">"; 0758 m_html += i18n("Schedule"); 0759 m_html += "</td>"; 0760 m_html += "<td class=\"left\" width=\"20%\">"; 0761 m_html += i18n("Account"); 0762 m_html += "</td>"; 0763 m_html += "<td class=\"right nowrap\" width=\"15%\">"; 0764 m_html += i18n("Amount"); 0765 m_html += "</td>"; 0766 m_html += "<td class=\"right nowrap\" width=\"15%\">"; 0767 m_html += i18n("Balance after"); 0768 m_html += "</td>"; 0769 m_html += "</tr>"; 0770 0771 // show all or the first 6 entries 0772 int cnt; 0773 cnt = (m_showAllSchedules) ? -1 : 6; 0774 bool needMoreLess = m_showAllSchedules; 0775 0776 QDate lastDate = QDate::currentDate().addMonths(1); 0777 std::sort(schedule.begin(), schedule.end()); 0778 int i = 0; 0779 do { 0780 it = schedule.begin(); 0781 if (it == schedule.end()) 0782 break; 0783 0784 // if the next due date is invalid (schedule is finished) 0785 // we remove it from the list 0786 QDate nextDate = (*it).nextDueDate(); 0787 if (!nextDate.isValid()) { 0788 schedule.erase(it); 0789 continue; 0790 } 0791 0792 if (nextDate > lastDate) 0793 break; 0794 0795 if (cnt == 0) { 0796 needMoreLess = true; 0797 break; 0798 } 0799 0800 // in case we've shown the current recurrence as overdue, 0801 // we don't show it here again, but keep the schedule 0802 // as it might show up later in the list again 0803 if (!(*it).isOverdue()) { 0804 if (cnt > 0) 0805 --cnt; 0806 i = showPaymentEntry(*it, i); 0807 0808 // for single occurrence we have reported everything so we 0809 // better get out of here. 0810 if ((*it).occurrence() == Schedule::Occurrence::Once) { 0811 schedule.erase(it); 0812 continue; 0813 } 0814 } 0815 0816 // if nextPayment returns an invalid date, setNextDueDate will 0817 // just skip it, resulting in a loop 0818 // we check the resulting date and erase the schedule if invalid 0819 if (!((*it).nextPayment((*it).nextDueDate())).isValid()) { 0820 schedule.erase(it); 0821 continue; 0822 } 0823 0824 (*it).setNextDueDate((*it).nextPayment((*it).nextDueDate())); 0825 std::sort(schedule.begin(), schedule.end()); 0826 } while (1); 0827 0828 if (needMoreLess) { 0829 m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd"); 0830 m_html += "<td colspan=\"5\">"; 0831 if (m_showAllSchedules) { 0832 m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18nc("Less...", "Show fewer schedules on the list") + linkend(); 0833 } else { 0834 m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18nc("More...", "Show more schedules on the list") + linkend(); 0835 } 0836 m_html += "</td>"; 0837 m_html += "</tr>"; 0838 } 0839 m_html += "</table></td></tr>"; 0840 } 0841 } 0842 m_html += "</table>"; 0843 } 0844 0845 int showPaymentEntry(const MyMoneySchedule& sched, int index, int cnt = 1) 0846 { 0847 MyMoneyFile* file = MyMoneyFile::instance(); 0848 0849 try { 0850 MyMoneyAccount mainAccount = sched.account(); 0851 if (!mainAccount.id().isEmpty()) { 0852 MyMoneyTransaction t = sched.transaction(); 0853 // only show the entry, if it is still active 0854 if (!sched.isFinished()) { 0855 // walk over splits in two rounds: the first takes 0856 // care of the main split and the second of all 0857 // others 0858 bool processMainSplit(true); 0859 do { 0860 const auto splits = t.splits(); 0861 for (const auto& sp : qAsConst(splits)) { 0862 if (processMainSplit == (sp.accountId() == mainAccount.id())) { 0863 const auto account = file->account(sp.accountId()); 0864 if (account.isAssetLiability()) { 0865 QString tmp = QString("<tr class=\"row-%1\">").arg(index++ & 0x01 ? "even" : "odd"); 0866 0867 // show payment date 0868 tmp += 0869 QString("<td class=\"nowrap\">") + MyMoneyUtils::formatDate(sched.adjustedNextDueDate()) + "</td><td class=\"center\">"; 0870 0871 // show Enter Next and Skip Next buttons 0872 if (!pathEnterIcon.isEmpty()) 0873 tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=enter").arg(sched.id()), i18n("Enter schedule")) 0874 + QString("<img src=\"%1\" border=\"0\" style=\"height:%2px;\" ></a>").arg(pathEnterIcon).arg(m_adjustedIconSize) 0875 + linkend(); 0876 tmp += "</td><td class=\"center\">"; 0877 if (!pathSkipIcon.isEmpty()) 0878 tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=skip").arg(sched.id()), i18n("Skip schedule")) 0879 + QString("<img src=\"%1\" border=\"0\" style=\"height:%2px;\"></a>").arg(pathSkipIcon).arg(m_adjustedIconSize) 0880 + linkend(); 0881 tmp += "</td><td>"; 0882 0883 tmp += 0884 link(VIEW_SCHEDULE, QString("?id=%1&mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend(); 0885 0886 // show quantity of payments overdue if any 0887 if (cnt > 1) 0888 tmp += i18np(" (%1 payment)", " (%1 payments)", cnt); 0889 0890 // show account of the main split 0891 tmp += "</td><td>"; 0892 tmp += link(VIEW_LEDGER, QString("?id=%1").arg(account.id())) + account.name() + linkend(); 0893 0894 // show amount of the schedule 0895 tmp += "</td><td class=\"right nowrap\">"; 0896 0897 const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(account.currencyId()); 0898 MyMoneyMoney payment = MyMoneyMoney(sp.value(t.commodity(), account.currencyId()) * cnt); 0899 QString amount = MyMoneyUtils::formatMoney(payment, account, currency); 0900 amount.replace(QChar(' '), " "); 0901 tmp += showColoredAmount(amount, payment.isNegative()); 0902 tmp += "</td>"; 0903 // show balance after payments 0904 tmp += "<td class=\"right nowrap\">"; 0905 QDate paymentDate = QDate(sched.adjustedNextDueDate()); 0906 MyMoneyMoney balanceAfter = forecastPaymentBalance(account, payment, paymentDate); 0907 QString balance = MyMoneyUtils::formatMoney(balanceAfter, account, currency); 0908 balance.replace(QChar(' '), " "); 0909 tmp += showColoredAmount(balance, balanceAfter.isNegative()); 0910 tmp += "</td></tr>"; 0911 0912 // qDebug("paymentEntry = '%s'", tmp.toLatin1()); 0913 m_html += tmp; 0914 } 0915 } 0916 } 0917 if (processMainSplit) { 0918 processMainSplit = false; 0919 continue; 0920 } 0921 break; 0922 } while (true); 0923 } 0924 } 0925 } catch (const MyMoneyException &e) { 0926 qDebug("Unable to display schedule entry: %s", e.what()); 0927 } 0928 return index; 0929 } 0930 0931 void showAccounts(paymentTypeE type, const QString& header) 0932 { 0933 MyMoneyFile* file = MyMoneyFile::instance(); 0934 int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); 0935 QList<MyMoneyAccount> accounts; 0936 0937 const auto showAllAccounts = KMyMoneySettings::showAllAccounts(); 0938 0939 // get list of all accounts 0940 file->accountList(accounts); 0941 for (QList<MyMoneyAccount>::Iterator it = accounts.begin(); it != accounts.end();) { 0942 bool removeAccount = false; 0943 if (!(*it).isClosed() || showAllAccounts) { 0944 switch ((*it).accountType()) { 0945 case Account::Type::Expense: 0946 case Account::Type::Income: 0947 // never show a category account 0948 // Note: This might be different in a future version when 0949 // the homepage also shows category based information 0950 removeAccount = true; 0951 break; 0952 0953 // Asset and Liability accounts are only shown if they 0954 // have the preferred flag set 0955 case Account::Type::Asset: 0956 case Account::Type::Liability: 0957 case Account::Type::Investment: 0958 // if preferred accounts are requested, then keep in list 0959 if (!(*it).value("PreferredAccount", false) || (type & Preferred) == 0) { 0960 removeAccount = true; 0961 } 0962 break; 0963 0964 // Check payment accounts. If payment and preferred is selected, 0965 // then always show them. If only payment is selected, then 0966 // show only if preferred flag is not set. 0967 case Account::Type::Checkings: 0968 case Account::Type::Savings: 0969 case Account::Type::Cash: 0970 case Account::Type::CreditCard: 0971 switch (type & (Payment | Preferred)) { 0972 case Payment: 0973 if ((*it).value("PreferredAccount", false)) 0974 removeAccount = true; 0975 break; 0976 0977 case Preferred: 0978 if (!(*it).value("PreferredAccount", false)) 0979 removeAccount = true; 0980 break; 0981 0982 case Payment | Preferred: 0983 break; 0984 0985 default: 0986 removeAccount = true; 0987 break; 0988 } 0989 break; 0990 0991 // filter all accounts that are not used on homepage views 0992 default: 0993 removeAccount = true; 0994 break; 0995 } 0996 0997 } else if ((*it).isClosed() || (*it).isInvest()) { 0998 // don't show if closed or a stock account 0999 removeAccount = true; 1000 } 1001 1002 if (removeAccount) 1003 it = accounts.erase(it); 1004 else 1005 ++it; 1006 } 1007 1008 if (!accounts.isEmpty()) { 1009 // sort the accounts by name 1010 std::stable_sort(accounts.begin(), accounts.end(), accountNameLess); 1011 int i = 0; 1012 1013 // print header 1014 m_html += 1015 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 1016 "<tr><td class=\"summaryheader\">" 1017 + header + "</td></tr>"; 1018 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1019 1020 m_html += "<tr class=\"item\">"; 1021 1022 if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { 1023 m_html += QString("<td class=\"center\"><img src=\"%1\" border=\"0\"></td>").arg(pathStatusHeader); 1024 } 1025 1026 m_html += "<td class=\"left\" width=\"80%\">"; 1027 m_html += i18n("Account"); 1028 m_html += "</td>"; 1029 1030 if (KMyMoneySettings::showCountOfUnmarkedTransactions()) 1031 m_html += QString("<td width=\"1%\" class=\"center\">%1</td>").arg(i18nc("Header not marked", "!M")); 1032 1033 if (KMyMoneySettings::showCountOfClearedTransactions()) 1034 m_html += QString("<td width=\"1%\" class=\"center\">%1</td>").arg(i18nc("Header cleared", "C")); 1035 1036 if (KMyMoneySettings::showCountOfNotReconciledTransactions()) 1037 m_html += QString("<td width=\"1%\" class=\"center\">%1</td>").arg(i18nc("Header not reconciled", "!R")); 1038 1039 if (KMyMoneySettings::showDateOfLastReconciliation()) 1040 m_html += QString("<td>%1</td>").arg(i18n("Reconciled")); 1041 1042 m_html += "<td width=\"10%\" class=\"right nowrap\">"; 1043 m_html += i18n("Balance"); 1044 m_html += "</td>"; 1045 1046 //only show limit info if user chose to do so 1047 if (KMyMoneySettings::showLimitInfo()) { 1048 m_html += "<td width=\"10%\" class=\"right nowrap\">"; 1049 m_html += i18n("To Minimum Balance<br>/ Maximum Credit"); 1050 m_html += "</td>"; 1051 } 1052 m_html += "</tr>"; 1053 1054 m_total = 0; 1055 QList<MyMoneyAccount>::const_iterator it_m; 1056 for (it_m = accounts.constBegin(); it_m != accounts.constEnd(); ++it_m) { 1057 m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd"); 1058 showAccountEntry(*it_m); 1059 m_html += "</tr>"; 1060 } 1061 m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd"); 1062 QString amount = m_total.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1063 if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) m_html += "<td></td>"; 1064 m_html += QString("<td class=\"right nowrap\"><b>%1</b></td>").arg(i18n("Total")); 1065 if (KMyMoneySettings::showCountOfUnmarkedTransactions()) m_html += "<td></td>"; 1066 if (KMyMoneySettings::showCountOfClearedTransactions()) m_html += "<td></td>"; 1067 if (KMyMoneySettings::showCountOfNotReconciledTransactions()) m_html += "<td></td>"; 1068 if (KMyMoneySettings::showDateOfLastReconciliation()) m_html += "<td></td>"; 1069 m_html += QString("<td class=\"right nowrap\"><b>%1</b></td></tr>").arg(showColoredAmount(amount, m_total.isNegative())); 1070 m_html += "</tr>"; 1071 m_html += "</table></td></tr>"; 1072 m_html += "</table>"; 1073 } 1074 } 1075 1076 void showFavoriteReports() 1077 { 1078 QList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList(); 1079 1080 if (!reports.isEmpty()) { 1081 std::stable_sort(reports.begin(), reports.end(), [&](const MyMoneyReport& rep1, const MyMoneyReport& rep2) { 1082 return rep1.name().localeAwareCompare(rep2.name()) < 0; 1083 }); 1084 bool firstTime = 1; 1085 int row = 0; 1086 QList<MyMoneyReport>::const_iterator it_report = reports.constBegin(); 1087 while (it_report != reports.constEnd()) { 1088 if ((*it_report).isFavorite()) { 1089 if (firstTime) { 1090 m_html += 1091 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 1092 "<tr><td class=\"summaryheader\">" 1093 + i18n("Favorite Reports") + "</td></tr>"; 1094 1095 m_html += "<tr class=\"gap\"><td> \n</td></tr>"; 1096 m_html += "<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1097 m_html += "<tr class=\"item\"><td class=\"left\" width=\"40%\">"; 1098 m_html += i18n("Report"); 1099 m_html += "</td><td width=\"60%\" class=\"left\">"; 1100 m_html += i18n("Comment"); 1101 m_html += "</td></tr>"; 1102 firstTime = false; 1103 } 1104 1105 m_html += QString("<tr class=\"row-%1\"><td>%2%3%4</td><td align=\"left\">%5</td></tr>") 1106 .arg(row++ & 0x01 ? "even" : "odd", 1107 link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id())), 1108 (*it_report).name(), 1109 linkend(), 1110 (*it_report).comment()); 1111 } 1112 1113 ++it_report; 1114 } 1115 if (!firstTime) { 1116 m_html += "</table></td></tr>"; 1117 m_html += "</table>"; 1118 } 1119 } 1120 } 1121 1122 void showForecast() 1123 { 1124 MyMoneyFile* file = MyMoneyFile::instance(); 1125 QList<MyMoneyAccount> accList; 1126 1127 //if forecast has not been executed yet, do it. 1128 if (!m_forecast.isForecastDone()) 1129 doForecast(); 1130 1131 accList = m_forecast.accountList(); 1132 1133 if (accList.count() > 0) { 1134 // sort the accounts by name 1135 std::stable_sort(accList.begin(), accList.end(), accountNameLess); 1136 auto i = 0; 1137 1138 auto colspan = 1; 1139 //get begin day 1140 auto beginDay = QDate::currentDate().daysTo(m_forecast.beginForecastDate()); 1141 //if begin day is today skip to next cycle 1142 if (beginDay == 0) 1143 beginDay = m_forecast.accountsCycle(); 1144 1145 // Now output header 1146 m_html += 1147 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 1148 "<tr><td class=\"summaryheader\">" 1149 + i18ncp("Forecast days", "%1 Day Forecast", "%1 Day Forecast", m_forecast.forecastDays()) + "</td></tr>"; 1150 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1151 1152 auto numberOfColumns = m_forecast.forecastDays() / m_forecast.accountsCycle(); 1153 auto colWidth = 55 / numberOfColumns; 1154 m_html += QString("<tr class=\"item\"><td class=\"left\" width=\"%1%\">").arg(100 - colWidth * numberOfColumns); 1155 m_html += i18n("Account"); 1156 m_html += "</td>"; 1157 for (i = 0; (i*m_forecast.accountsCycle() + beginDay) <= m_forecast.forecastDays(); ++i) { 1158 m_html += QString("<td width=\"%1%\" class=\"right nowrap\">").arg(colWidth); 1159 1160 m_html += i18ncp("Forecast days", "%1 day", "%1 days", i * m_forecast.accountsCycle() + beginDay); 1161 m_html += "</td>"; 1162 colspan++; 1163 } 1164 m_html += "</tr>"; 1165 1166 // Now output entries 1167 i = 0; 1168 1169 QList<MyMoneyAccount>::ConstIterator it_account; 1170 for (it_account = accList.constBegin(); it_account != accList.constEnd(); ++it_account) { 1171 //MyMoneyAccount acc = (*it_n); 1172 1173 m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd"); 1174 m_html += QString("<td width=\"40%\">") + 1175 link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "</td>"; 1176 1177 qint64 dropZero = -1; //account dropped below zero 1178 qint64 dropMinimum = -1; //account dropped below minimum balance 1179 QString minimumBalance = (*it_account).value("minimumBalance"); 1180 MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); 1181 MyMoneySecurity currency; 1182 MyMoneyMoney forecastBalance; 1183 1184 //change account to deep currency if account is an investment 1185 if ((*it_account).isInvest()) { 1186 MyMoneySecurity underSecurity = file->security((*it_account).currencyId()); 1187 currency = file->security(underSecurity.tradingCurrency()); 1188 } else { 1189 currency = file->security((*it_account).currencyId()); 1190 } 1191 1192 for (auto f = beginDay; f <= m_forecast.forecastDays(); f += m_forecast.accountsCycle()) { 1193 forecastBalance = m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f)); 1194 QString amount; 1195 amount = MyMoneyUtils::formatMoney(forecastBalance, *it_account, currency); 1196 amount.replace(QChar(' '), " "); 1197 m_html += QString("<td width=\"%1%\" class=\"right nowrap\">").arg(colWidth); 1198 m_html += QString("%1</td>").arg(showColoredAmount(amount, forecastBalance.isNegative())); 1199 } 1200 1201 m_html += "</tr>"; 1202 1203 //Check if the account is going to be below zero or below the minimal balance in the forecast period 1204 1205 //Check if the account is going to be below minimal balance 1206 dropMinimum = m_forecast.daysToMinimumBalance(*it_account); 1207 1208 //Check if the account is going to be below zero in the future 1209 dropZero = m_forecast.daysToZeroBalance(*it_account); 1210 1211 1212 // spit out possible warnings 1213 QString msg; 1214 1215 // if a minimum balance has been specified, an appropriate warning will 1216 // only be shown, if the drop below 0 is on a different day or not present 1217 1218 if (dropMinimum != -1 1219 && !minBalance.isZero() 1220 && (dropMinimum < dropZero 1221 || dropZero == -1)) { 1222 switch (dropMinimum) { 1223 case 0: 1224 msg = i18n("The balance of %1 is below the minimum balance %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); 1225 msg = showColoredAmount(msg, true); 1226 break; 1227 default: 1228 msg = i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.", 1229 "The balance of %2 will drop below the minimum balance %3 in %1 days.", 1230 dropMinimum - 1, (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); 1231 msg = showColoredAmount(msg, true); 1232 break; 1233 } 1234 1235 if (!msg.isEmpty()) { 1236 m_html += QString("<tr class=\"warning\" style=\"font-weight: normal;\" ><td colspan=%2 align=\"center\" >%1</td></tr>").arg(msg).arg(colspan); 1237 } 1238 } 1239 // a drop below zero is always shown 1240 msg.clear(); 1241 switch (dropZero) { 1242 case -1: 1243 break; 1244 case 0: 1245 if ((*it_account).accountGroup() == Account::Type::Asset) { 1246 msg = i18n("The balance of %1 is below %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); 1247 msg = showColoredAmount(msg, true); 1248 break; 1249 } 1250 if ((*it_account).accountGroup() == Account::Type::Liability) { 1251 msg = i18n("The balance of %1 is above %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); 1252 break; 1253 } 1254 break; 1255 default: 1256 if ((*it_account).accountGroup() == Account::Type::Asset) { 1257 msg = i18np("The balance of %2 will drop below %3 in %1 day.", 1258 "The balance of %2 will drop below %3 in %1 days.", 1259 dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); 1260 msg = showColoredAmount(msg, true); 1261 break; 1262 } 1263 if ((*it_account).accountGroup() == Account::Type::Liability) { 1264 msg = i18np("The balance of %2 will raise above %3 in %1 day.", 1265 "The balance of %2 will raise above %3 in %1 days.", 1266 dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); 1267 break; 1268 } 1269 } 1270 if (!msg.isEmpty()) { 1271 m_html += QString("<tr class=\"warning\"><td colspan=%2 align=\"center\" ><b>%1</b></td></tr>").arg(msg).arg(colspan); 1272 } 1273 } 1274 m_html += "</tr>"; 1275 m_html += "</table></td></tr>"; 1276 m_html += "</table>"; 1277 } 1278 } 1279 1280 QString link(const QString& view, const QString& query, const QString& _title = QString()) const 1281 { 1282 QString titlePart; 1283 QString title(_title); 1284 if (!title.isEmpty()) 1285 titlePart = QString(" title=\"%1\"").arg(title.replace(QLatin1Char(' '), " ")); 1286 1287 return QString("<a href=\"/%1%2\"%3>").arg(view, query, titlePart); 1288 } 1289 1290 QString linkend() const 1291 { 1292 return QStringLiteral("</a>"); 1293 } 1294 1295 void showAssetsLiabilities() 1296 { 1297 QList<MyMoneyAccount> accounts; 1298 QList<MyMoneyAccount>::ConstIterator it; 1299 QList<MyMoneyAccount> assets; 1300 QList<MyMoneyAccount> liabilities; 1301 MyMoneyMoney netAssets; 1302 MyMoneyMoney netLiabilities; 1303 1304 MyMoneyFile* file = MyMoneyFile::instance(); 1305 int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); 1306 int i = 0; 1307 1308 // get list of all accounts 1309 file->accountList(accounts); 1310 1311 const auto showAllAccounts = KMyMoneySettings::showAllAccounts(); 1312 const bool hideZeroBalanceAccounts = KMyMoneySettings::hideZeroBalanceAccounts() && !showAllAccounts; 1313 1314 for (it = accounts.constBegin(); it != accounts.constEnd();) { 1315 if (!(*it).isClosed() || showAllAccounts) { 1316 const auto value = MyMoneyFile::instance()->balance((*it).id(), QDate::currentDate()); 1317 switch ((*it).accountType()) { 1318 // group all assets into one list but make sure that investment accounts always show up 1319 case Account::Type::Investment: 1320 // for investment accounts we also need to check the sub-accounts 1321 if (value.isZero()) { 1322 const auto subAccountList = (*it).accountList(); 1323 for (const auto& accId : qAsConst(subAccountList)) { 1324 const auto subValue = MyMoneyFile::instance()->balance(accId, QDate::currentDate()); 1325 if (!(subValue.isZero() && hideZeroBalanceAccounts)) { 1326 assets << *it; 1327 break; 1328 } 1329 } 1330 } else { 1331 assets << *it; 1332 } 1333 break; 1334 1335 case Account::Type::Checkings: 1336 case Account::Type::Savings: 1337 case Account::Type::Cash: 1338 case Account::Type::Asset: 1339 case Account::Type::AssetLoan: 1340 // list account if it's the last in the hierarchy or has transactions in it 1341 // and listing zero balance account is not suppressed 1342 if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { 1343 if (!(value.isZero() && hideZeroBalanceAccounts)) { 1344 assets << *it; 1345 } 1346 } 1347 break; 1348 1349 // group the liabilities into the other 1350 case Account::Type::CreditCard: 1351 case Account::Type::Liability: 1352 case Account::Type::Loan: 1353 // list account if it's the last in the hierarchy or has transactions in it 1354 // and listing zero balance account is not suppressed 1355 if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { 1356 if (!(value.isZero() && hideZeroBalanceAccounts)) { 1357 liabilities << *it; 1358 } 1359 } 1360 break; 1361 1362 default: 1363 break; 1364 } 1365 } 1366 ++it; 1367 } 1368 1369 //only do it if we have assets or liabilities account 1370 if (assets.count() > 0 || liabilities.count() > 0) { 1371 // sort the accounts by name 1372 std::stable_sort(assets.begin(), assets.end(), accountNameLess); 1373 std::stable_sort(liabilities.begin(), liabilities.end(), accountNameLess); 1374 QString statusHeader; 1375 if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { 1376 statusHeader = QString("<img src=\"%1\" border=\"0\">").arg(pathStatusHeader); 1377 } 1378 1379 //print header 1380 m_html += 1381 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 1382 "<tr><td class=\"summaryheader\">" 1383 + i18n("Assets and Liabilities Summary") + "</td></tr>"; 1384 m_html += "<tr><td><table align=\"center\" width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1385 1386 //column titles 1387 1388 m_html += "<tr class=\"item\">"; 1389 1390 if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { 1391 m_html += "<td class=\"setcolor center\">"; 1392 m_html += statusHeader; 1393 m_html += "</td>"; 1394 } 1395 1396 m_html += "<td class=\"left\" width=\"30%\">"; 1397 m_html += i18n("Asset Accounts"); 1398 m_html += "</td>"; 1399 1400 if (KMyMoneySettings::showCountOfUnmarkedTransactions()) 1401 m_html += "<td width=\"1%\" class=\"setcolor center\">!M</td>"; 1402 1403 if (KMyMoneySettings::showCountOfClearedTransactions()) 1404 m_html += "<td width=\"1%\" class=\"setcolor center\">C</td>"; 1405 1406 if (KMyMoneySettings::showCountOfNotReconciledTransactions()) 1407 m_html += "<td width=\"1%\" class=\"setcolor center\">!R</td>"; 1408 1409 if (KMyMoneySettings::showDateOfLastReconciliation()) 1410 m_html += "<td width=\"1%\" class=\"setcolor\">" + i18n("Reconciled") + "</td>"; 1411 1412 m_html += "<td width=\"5%\" class=\"right nowrap\">"; 1413 m_html += i18n("Balance"); 1414 m_html += "</td>"; 1415 1416 //intermediate row to separate both columns 1417 m_html += "<td width=\"5%\" class=\"setcolor\"></td>"; 1418 1419 if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) { 1420 m_html += "<td class=\"setcolor center\">"; 1421 m_html += statusHeader; 1422 m_html += "</td>"; 1423 } 1424 1425 m_html += "<td class=\"left\" width=\"30%\">"; 1426 m_html += i18n("Liability Accounts"); 1427 m_html += "</td>"; 1428 1429 if (KMyMoneySettings::showCountOfUnmarkedTransactions()) 1430 m_html += "<td width=\"1%\" class=\"setcolor nowrap\">!M</td>"; 1431 1432 if (KMyMoneySettings::showCountOfClearedTransactions()) 1433 m_html += "<td width=\"1%\" class=\"setcolor nowrap\">C</td>"; 1434 1435 if (KMyMoneySettings::showCountOfNotReconciledTransactions()) 1436 m_html += "<td width=\"1%\" class=\"setcolor nowrap\">!R</td>"; 1437 1438 if (KMyMoneySettings::showDateOfLastReconciliation()) 1439 m_html += "<td width=\"1%\" class=\"setcolor nowrap\">" + i18n("Reconciled") + "</td>"; 1440 1441 m_html += "<td width=\"5%\" class=\"right nowrap\">"; 1442 m_html += i18n("Balance"); 1443 m_html += "</td></tr>"; 1444 1445 QString placeHolder_Status, placeHolder_Counts; 1446 if (KMyMoneySettings::showBalanceStatusOfOnlineAccounts()) placeHolder_Status = "<td></td>"; 1447 if (KMyMoneySettings::showCountOfUnmarkedTransactions()) placeHolder_Counts = "<td></td>"; 1448 if (KMyMoneySettings::showCountOfClearedTransactions()) placeHolder_Counts += "<td></td>"; 1449 if (KMyMoneySettings::showCountOfNotReconciledTransactions()) placeHolder_Counts += "<td></td>"; 1450 if (KMyMoneySettings::showDateOfLastReconciliation()) placeHolder_Counts += "<td></td>"; 1451 1452 //get asset and liability accounts 1453 QList<MyMoneyAccount>::const_iterator asset_it = assets.constBegin(); 1454 QList<MyMoneyAccount>::const_iterator liabilities_it = liabilities.constBegin(); 1455 for (; asset_it != assets.constEnd() || liabilities_it != liabilities.constEnd();) { 1456 m_html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd"); 1457 //write an asset account if we still have any 1458 if (asset_it != assets.constEnd()) { 1459 MyMoneyMoney value; 1460 //investment accounts consolidate the balance of its subaccounts 1461 if ((*asset_it).accountType() == Account::Type::Investment) { 1462 value = investmentBalance(*asset_it); 1463 } else { 1464 value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate()); 1465 } 1466 1467 //calculate balance for foreign currency accounts 1468 if ((*asset_it).currencyId() != file->baseCurrency().id()) { 1469 const auto curPrice = file->price((*asset_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); 1470 const auto curRate = curPrice.rate(file->baseCurrency().id()); 1471 auto baseValue = value * curRate; 1472 baseValue = baseValue.convert(10000); 1473 netAssets += baseValue; 1474 } else { 1475 netAssets += value; 1476 } 1477 //show the account without minimum balance 1478 showAccountEntry(*asset_it, value, MyMoneyMoney(), false); 1479 ++asset_it; 1480 } else { 1481 //write a white space if we don't 1482 m_html += QString("%1<td></td>%2<td></td>").arg(placeHolder_Status, placeHolder_Counts); 1483 } 1484 1485 //leave the intermediate column empty 1486 m_html += "<td class=\"setcolor\"></td>"; 1487 1488 //write a liability account 1489 if (liabilities_it != liabilities.constEnd()) { 1490 MyMoneyMoney value; 1491 value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate()); 1492 //calculate balance if foreign currency 1493 if ((*liabilities_it).currencyId() != file->baseCurrency().id()) { 1494 const auto curPrice = file->price((*liabilities_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); 1495 const auto curRate = curPrice.rate(file->baseCurrency().id()); 1496 auto baseValue = value * curRate; 1497 baseValue = baseValue.convert(10000); 1498 netLiabilities += baseValue; 1499 } else { 1500 netLiabilities += value; 1501 } 1502 //show the account without minimum balance 1503 showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false); 1504 ++liabilities_it; 1505 } else { 1506 //leave the space empty if we run out of liabilities 1507 m_html += QString("%1<td></td>%2<td></td>").arg(placeHolder_Status, placeHolder_Counts); 1508 } 1509 m_html += "</tr>"; 1510 } 1511 1512 //calculate net worth 1513 MyMoneyMoney netWorth = netAssets + netLiabilities; 1514 1515 //format assets, liabilities and net worth 1516 QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1517 QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1518 QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1519 amountAssets.replace(QChar(' '), " "); 1520 amountLiabilities.replace(QChar(' '), " "); 1521 amountNetWorth.replace(QChar(' '), " "); 1522 1523 m_html += QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd"); 1524 1525 //print total for assets 1526 m_html += QString("%1<td class=\"left\">%2</td>%3<td class=\"right nowrap\">%4</td>") 1527 .arg(placeHolder_Status, i18n("Total Assets"), placeHolder_Counts, showColoredAmount(amountAssets, netAssets.isNegative())); 1528 1529 //leave the intermediate column empty 1530 m_html += "<td class=\"setcolor\"></td>"; 1531 1532 //print total liabilities 1533 m_html += 1534 QString("%1<td class=\"left\">%2</td>%3<td class=\"right nowrap\">%4</td>") 1535 .arg(placeHolder_Status, i18n("Total Liabilities"), placeHolder_Counts, showColoredAmount(amountLiabilities, netLiabilities.isNegative())); 1536 m_html += "</tr>"; 1537 1538 //print net worth 1539 m_html += QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd"); 1540 1541 m_html += QString("%1<td></td><td></td>%2<td class=\"setcolor\"></td>").arg(placeHolder_Status, placeHolder_Counts); 1542 m_html += QString("%1<td class=\"left\">%2</td>%3<td class=\"right nowrap\">%4</td>") 1543 .arg(placeHolder_Status, i18n("Net Worth"), placeHolder_Counts, showColoredAmount(amountNetWorth, netWorth.isNegative())); 1544 1545 m_html += "</tr>"; 1546 m_html += "</table></td></tr>"; 1547 m_html += "</table>"; 1548 } 1549 } 1550 1551 void showBudget() 1552 { 1553 QVariant variantReport; 1554 1555 if (const auto reportsPlugin = pPlugins.data.value(QStringLiteral("reportsview"), nullptr)) { 1556 variantReport = reportsPlugin->requestData(QString(), eWidgetPlugin::WidgetType::Budget); 1557 } 1558 1559 if (!variantReport.isNull()) { 1560 m_html.append(variantReport.toString()); 1561 } else { 1562 m_html += 1563 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 1564 "<tr><td class=\"summaryheader\">" 1565 + i18n("Budget") + "</td></tr>"; 1566 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1567 1568 m_html += "<tr>"; 1569 m_html += QString("<td><center>%1</center></td>").arg(i18n("Enable reports plugin to see this chart.")); 1570 m_html += "</tr>"; 1571 m_html += "</table></td></tr>"; 1572 m_html += "</table>"; 1573 } 1574 } 1575 1576 void showCashFlowSummary() 1577 { 1578 MyMoneyTransactionFilter filter; 1579 MyMoneyMoney incomeValue; 1580 MyMoneyMoney expenseValue; 1581 1582 MyMoneyFile* file = MyMoneyFile::instance(); 1583 int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); 1584 1585 //set start and end of month dates 1586 QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1); 1587 QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth()); 1588 1589 //Add total income and expenses for this month 1590 //get transactions for current month 1591 filter.setDateFilter(startOfMonth, endOfMonth); 1592 filter.setReportAllSplits(false); 1593 1594 QList<MyMoneyTransaction> transactions; 1595 file->transactionList(transactions, filter); 1596 //if no transaction then skip and print total in zero 1597 if (transactions.size() > 0) { 1598 1599 //get all transactions for this month 1600 for (const auto& transaction : qAsConst(transactions)) { 1601 //get the splits for each transaction 1602 const auto splits = transaction.splits(); 1603 for (const auto& split : qAsConst(splits)) { 1604 if (!split.shares().isZero()) { 1605 auto repSplitAcc = file->account(split.accountId()); 1606 1607 //only add if it is an income or expense 1608 if (repSplitAcc.isIncomeExpense()) { 1609 MyMoneyMoney value; 1610 1611 //convert to base currency if necessary 1612 if (repSplitAcc.currencyId() != file->baseCurrency().id()) { 1613 const auto curPrice = file->price(repSplitAcc.tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); 1614 const auto curRate = curPrice.rate(file->baseCurrency().id()); 1615 value = (split.shares() * MyMoneyMoney::MINUS_ONE) * curRate; 1616 value = value.convert(10000); 1617 } else { 1618 value = (split.shares() * MyMoneyMoney::MINUS_ONE); 1619 } 1620 1621 //store depending on account type 1622 if (repSplitAcc.accountType() == Account::Type::Income) { 1623 incomeValue += value; 1624 } else { 1625 expenseValue += value; 1626 } 1627 } 1628 } 1629 } 1630 } 1631 } 1632 1633 //format income and expenses 1634 QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1635 QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1636 amountIncome.replace(QChar(' '), " "); 1637 amountExpense.replace(QChar(' '), " "); 1638 1639 //calculate schedules 1640 1641 //Add all schedules for this month 1642 MyMoneyMoney scheduledIncome; 1643 MyMoneyMoney scheduledExpense; 1644 MyMoneyMoney scheduledLiquidTransfer; 1645 MyMoneyMoney scheduledOtherTransfer; 1646 1647 //get overdues and schedules until the end of this month 1648 QList<MyMoneySchedule> schedule = file->scheduleList(QString(), Schedule::Type::Any, 1649 Schedule::Occurrence::Any, 1650 Schedule::PaymentType::Any, 1651 QDate(), 1652 endOfMonth, false); 1653 1654 //Remove the finished schedules 1655 QList<MyMoneySchedule>::Iterator finished_it; 1656 for (finished_it = schedule.begin(); finished_it != schedule.end();) { 1657 if ((*finished_it).isFinished()) { 1658 finished_it = schedule.erase(finished_it); 1659 continue; 1660 } 1661 ++finished_it; 1662 } 1663 1664 //add income and expenses 1665 QList<MyMoneySchedule>::Iterator sched_it; 1666 for (sched_it = schedule.begin(); sched_it != schedule.end();) { 1667 QDate nextDate = (*sched_it).nextDueDate(); 1668 int cnt = 0; 1669 1670 while (nextDate.isValid() && nextDate <= endOfMonth) { 1671 ++cnt; 1672 nextDate = (*sched_it).nextPayment(nextDate); 1673 // for single occurrence nextDate will not change, so we 1674 // better get out of here. 1675 if ((*sched_it).occurrence() == Schedule::Occurrence::Once) 1676 break; 1677 } 1678 1679 MyMoneyAccount acc = (*sched_it).account(); 1680 if (!acc.id().isEmpty()) { 1681 MyMoneyTransaction transaction = (*sched_it).transaction(); 1682 // only show the entry, if it is still active 1683 1684 MyMoneySplit sp = transaction.splitByAccount(acc.id(), true); 1685 1686 // take care of the autoCalc stuff 1687 if ((*sched_it).type() == Schedule::Type::LoanPayment) { 1688 nextDate = (*sched_it).nextPayment((*sched_it).lastPayment()); 1689 1690 //make sure we have all 'starting balances' so that the autocalc works 1691 QMap<QString, MyMoneyMoney> balanceMap; 1692 1693 for (const auto& split : qAsConst(transaction.splits())) { 1694 acc = file->account(split.accountId()); 1695 // collect all overdues on the first day 1696 QDate schedDate = nextDate; 1697 if (QDate::currentDate() >= nextDate) 1698 schedDate = QDate::currentDate().addDays(1); 1699 1700 balanceMap[acc.id()] += file->balance(acc.id(), QDate::currentDate()); 1701 } 1702 KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap); 1703 } 1704 1705 //go through the splits and assign to liquid or other transfers 1706 const QList<MyMoneySplit> splits = transaction.splits(); 1707 QList<MyMoneySplit>::const_iterator split_it; 1708 for (split_it = splits.constBegin(); split_it != splits.constEnd(); ++split_it) { 1709 if ((*split_it).accountId() != acc.id()) { 1710 auto repSplitAcc = file->account((*split_it).accountId()); 1711 1712 //get the shares and multiply by the quantity of occurrences in the period 1713 MyMoneyMoney value = (*split_it).shares() * cnt; 1714 1715 //convert to foreign currency if needed 1716 if (repSplitAcc.currencyId() != file->baseCurrency().id()) { 1717 const auto curPrice = file->price(repSplitAcc.tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); 1718 const auto curRate = curPrice.rate(file->baseCurrency().id()); 1719 value = value * curRate; 1720 value = value.convert(10000); 1721 } 1722 1723 if ((repSplitAcc.isLiquidLiability() 1724 || repSplitAcc.isLiquidAsset()) 1725 && acc.accountGroup() != repSplitAcc.accountGroup()) { 1726 scheduledLiquidTransfer += value; 1727 } else if (repSplitAcc.isAssetLiability() 1728 && !repSplitAcc.isLiquidLiability() 1729 && !repSplitAcc.isLiquidAsset()) { 1730 scheduledOtherTransfer += value; 1731 } else if (repSplitAcc.isIncomeExpense()) { 1732 //income and expenses are stored as negative values 1733 if (repSplitAcc.accountType() == Account::Type::Income) 1734 scheduledIncome -= value; 1735 if (repSplitAcc.accountType() == Account::Type::Expense) 1736 scheduledExpense -= value; 1737 } 1738 } 1739 } 1740 } 1741 ++sched_it; 1742 } 1743 1744 //format the currency strings 1745 QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1746 QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1747 QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1748 QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1749 1750 amountScheduledIncome.replace(QChar(' '), " "); 1751 amountScheduledExpense.replace(QChar(' '), " "); 1752 amountScheduledLiquidTransfer.replace(QChar(' '), " "); 1753 amountScheduledOtherTransfer.replace(QChar(' '), " "); 1754 1755 //get liquid assets and liabilities 1756 QList<MyMoneyAccount> accounts; 1757 QList<MyMoneyAccount>::const_iterator account_it; 1758 MyMoneyMoney liquidAssets; 1759 MyMoneyMoney liquidLiabilities; 1760 1761 // get list of all accounts 1762 file->accountList(accounts); 1763 for (account_it = accounts.constBegin(); account_it != accounts.constEnd();) { 1764 if (!(*account_it).isClosed()) { 1765 switch ((*account_it).accountType()) { 1766 //group all assets into one list 1767 case Account::Type::Checkings: 1768 case Account::Type::Savings: 1769 case Account::Type::Cash: { 1770 MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); 1771 //calculate balance for foreign currency accounts 1772 if ((*account_it).currencyId() != file->baseCurrency().id()) { 1773 const auto curPrice = file->price((*account_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); 1774 const auto curRate = curPrice.rate(file->baseCurrency().id()); 1775 auto baseValue = value * curRate; 1776 liquidAssets += baseValue; 1777 liquidAssets = liquidAssets.convert(10000); 1778 } else { 1779 liquidAssets += value; 1780 } 1781 break; 1782 } 1783 //group the liabilities into the other 1784 case Account::Type::CreditCard: { 1785 MyMoneyMoney value; 1786 value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); 1787 //calculate balance if foreign currency 1788 if ((*account_it).currencyId() != file->baseCurrency().id()) { 1789 const auto curPrice = file->price((*account_it).tradingCurrencyId(), file->baseCurrency().id(), QDate::currentDate()); 1790 const auto curRate = curPrice.rate(file->baseCurrency().id()); 1791 auto baseValue = value * curRate; 1792 liquidLiabilities += baseValue; 1793 liquidLiabilities = liquidLiabilities.convert(10000); 1794 } else { 1795 liquidLiabilities += value; 1796 } 1797 break; 1798 } 1799 default: 1800 break; 1801 } 1802 } 1803 ++account_it; 1804 } 1805 //calculate net worth 1806 MyMoneyMoney liquidWorth = liquidAssets + liquidLiabilities; 1807 1808 //format assets, liabilities and net worth 1809 QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1810 QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1811 QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1812 amountLiquidAssets.replace(QChar(' '), " "); 1813 amountLiquidLiabilities.replace(QChar(' '), " "); 1814 amountLiquidWorth.replace(QChar(' '), " "); 1815 1816 //show the summary 1817 m_html += 1818 "<table width=\"97%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\" class=\"displayblock\" >" 1819 "<tr><td class=\"summaryheader\">" 1820 + i18n("Cash Flow Summary") + "</td></tr>"; 1821 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1822 //income and expense title 1823 m_html += "<tr class=\"itemtitle\">"; 1824 m_html += "<td class=\"left\" colspan=\"4\">"; 1825 m_html += i18n("Income and Expenses of Current Month"); 1826 m_html += "</td></tr>"; 1827 //column titles 1828 m_html += "<tr class=\"item\">"; 1829 m_html += "<td width=\"25%\" class=\"center\">"; 1830 m_html += i18n("Income"); 1831 m_html += "</td>"; 1832 m_html += "<td width=\"25%\" class=\"center\">"; 1833 m_html += i18n("Scheduled Income"); 1834 m_html += "</td>"; 1835 m_html += "<td width=\"25%\" class=\"center\">"; 1836 m_html += i18n("Expenses"); 1837 m_html += "</td>"; 1838 m_html += "<td width=\"25%\" class=\"center\">"; 1839 m_html += i18n("Scheduled Expenses"); 1840 m_html += "</td>"; 1841 m_html += "</tr>"; 1842 1843 //add row with banding 1844 m_html += QString("<tr class=\"row-even\" style=\"font-weight:bold;\">"); 1845 1846 //print current income 1847 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountIncome, incomeValue.isNegative())); 1848 1849 //print the scheduled income 1850 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative())); 1851 1852 //print current expenses 1853 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountExpense, expenseValue.isNegative())); 1854 1855 //print the scheduled expenses 1856 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative())); 1857 m_html += "</tr>"; 1858 1859 m_html += "</table></td></tr>"; 1860 1861 //print header of assets and liabilities 1862 m_html += "<tr class=\"gap\"><td> \n</td></tr>"; 1863 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1864 //assets and liabilities title 1865 m_html += "<tr class=\"itemtitle\">"; 1866 m_html += "<td class=\"left\" colspan=\"4\">"; 1867 m_html += i18n("Liquid Assets and Liabilities"); 1868 m_html += "</td></tr>"; 1869 //column titles 1870 m_html += "<tr class=\"item\">"; 1871 m_html += "<td width=\"25%\" class=\"center\">"; 1872 m_html += i18n("Liquid Assets"); 1873 m_html += "</td>"; 1874 m_html += "<td width=\"25%\" class=\"center\">"; 1875 m_html += i18n("Transfers to Liquid Liabilities"); 1876 m_html += "</td>"; 1877 m_html += "<td width=\"25%\" class=\"center\">"; 1878 m_html += i18n("Liquid Liabilities"); 1879 m_html += "</td>"; 1880 m_html += "<td width=\"25%\" class=\"center\">"; 1881 m_html += i18n("Other Transfers"); 1882 m_html += "</td>"; 1883 m_html += "</tr>"; 1884 1885 //add row with banding 1886 m_html += QString("<tr class=\"row-even\" style=\"font-weight:bold;\">"); 1887 1888 //print current liquid assets 1889 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative())); 1890 1891 //print the scheduled transfers 1892 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative())); 1893 1894 //print current liabilities 1895 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative())); 1896 1897 //print the scheduled transfers 1898 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative())); 1899 1900 m_html += "</tr>"; 1901 1902 m_html += "</table></td></tr>"; 1903 1904 //final conclusion 1905 MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense; 1906 MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer; 1907 MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer; 1908 1909 QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1910 QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1911 QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); 1912 amountProfit.replace(QChar(' '), " "); 1913 amountExpectedAsset.replace(QChar(' '), " "); 1914 amountExpectedLiabilities.replace(QChar(' '), " "); 1915 1916 1917 1918 //print header of cash flow status 1919 m_html += "<tr class=\"gap\"><td> \n</td></tr>"; 1920 m_html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"; 1921 //income and expense title 1922 m_html += "<tr class=\"itemtitle\">"; 1923 m_html += "<td class=\"left\" colspan=\"4\">"; 1924 m_html += i18n("Cash Flow Status"); 1925 m_html += "</td></tr>"; 1926 //column titles 1927 m_html += "<tr class=\"item\">"; 1928 m_html += "<td colspan=\"2\" width=\"33%\" class=\"center\">"; 1929 m_html += i18n("Expected Liquid Assets"); 1930 m_html += "</td>"; 1931 m_html += "<td width=\"33%\" class=\"center\">"; 1932 m_html += i18n("Expected Liquid Liabilities"); 1933 m_html += "</td>"; 1934 m_html += "<td width=\"34%\" class=\"center\">"; 1935 m_html += i18n("Expected Profit/Loss"); 1936 m_html += "</td>"; 1937 m_html += "</tr>"; 1938 1939 //add row with banding 1940 m_html += QString("<tr class=\"row-even\" style=\"font-weight:bold;\">"); 1941 1942 //print expected assets 1943 m_html += QString("<td colspan=\"2\" class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative())); 1944 1945 //print expected liabilities 1946 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative())); 1947 1948 //print expected profit 1949 m_html += QString("<td class=\"right nowrap\">%2</td>").arg(showColoredAmount(amountProfit, profitValue.isNegative())); 1950 1951 m_html += "</tr>"; 1952 m_html += "</table></td></tr>"; 1953 m_html += "</table>"; 1954 } 1955 1956 1957 /** 1958 * daily balances of an account 1959 */ 1960 typedef QMap<QDate, MyMoneyMoney> dailyBalances; 1961 1962 KMMTextBrowser* m_view; 1963 1964 QString m_html; 1965 bool m_showAllSchedules; 1966 bool m_needLoad; 1967 bool m_skipRefresh; 1968 MyMoneyForecast m_forecast; 1969 MyMoneyMoney m_total; 1970 /** 1971 * Hold the last valid size of the net worth graph 1972 * for the times when the needed size can't be computed. 1973 */ 1974 QSize m_netWorthGraphLastValidSize; 1975 1976 QMap< QString, QVector<int> > m_transactionStats; 1977 1978 /** 1979 * daily forecast balance of accounts 1980 */ 1981 QMap<QString, dailyBalances> m_accountList; 1982 int m_scrollBarPos; 1983 bool m_fileOpen; 1984 1985 // Enter and Skip data pixmaps 1986 QString pathEnterIcon; 1987 QString pathSkipIcon; 1988 QString pathStatusHeader; // online download status 1989 int m_adjustedIconSize; 1990 double m_devRatio; 1991 QTimer m_resizeRefreshTimer; 1992 QSize m_startSize; 1993 }; 1994 1995 #endif