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(' '), "&nbsp;");
0180         if (showMinBal) {
0181             amountToMinBal = MyMoneyUtils::formatMoney(valueToMinBal, acc, currency);
0182             amountToMinBal.replace(QChar(' '), "&nbsp;");
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("<", "&lt;").replace(">", "&gt;");
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(' '), "&nbsp;");
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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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>&nbsp;\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>&nbsp;\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>&nbsp;\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>&nbsp;\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&amp;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&amp;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&amp;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(' '), "&nbsp;");
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(' '), "&nbsp;");
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>&nbsp;\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(' '), "&nbsp;");
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(' '), "&nbsp;"));
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(' '), "&nbsp;");
1520             amountLiabilities.replace(QChar(' '), "&nbsp;");
1521             amountNetWorth.replace(QChar(' '), "&nbsp;");
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(' '), "&nbsp;");
1637         amountExpense.replace(QChar(' '), "&nbsp;");
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(' '), "&nbsp;");
1751         amountScheduledExpense.replace(QChar(' '), "&nbsp;");
1752         amountScheduledLiquidTransfer.replace(QChar(' '), "&nbsp;");
1753         amountScheduledOtherTransfer.replace(QChar(' '), "&nbsp;");
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(' '), "&nbsp;");
1813         amountLiquidLiabilities.replace(QChar(' '), "&nbsp;");
1814         amountLiquidWorth.replace(QChar(' '), "&nbsp;");
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>&nbsp;\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(' '), "&nbsp;");
1913         amountExpectedAsset.replace(QChar(' '), "&nbsp;");
1914         amountExpectedLiabilities.replace(QChar(' '), "&nbsp;");
1915 
1916 
1917 
1918         //print header of cash flow status
1919         m_html += "<tr class=\"gap\"><td>&nbsp;\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