File indexing completed on 2024-06-16 04:46:41

0001 /*
0002 
0003     SPDX-FileCopyrightText: 2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-FileCopyrightText: 2021 Dawid Wróbel <me@dawidwrobel.com>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "reportsview.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 // ----------------------------------------------------------------------------
0014 // KDE Includes
0015 
0016 #include <KPluginFactory>
0017 #include <KLocalizedString>
0018 
0019 // ----------------------------------------------------------------------------
0020 // Project Includes
0021 
0022 #include "viewinterface.h"
0023 #include "kreportsview.h"
0024 #include "kreportchartview.h"
0025 #include "kmymoneysettings.h"
0026 #include "pivottable.h"
0027 #include "pivotgrid.h"
0028 #include "mymoneyfile.h"
0029 #include "mymoneysecurity.h"
0030 #include "mymoneyenums.h"
0031 #include "reportsviewenums.h"
0032 
0033 #define VIEW_LEDGER         "ledger"
0034 
0035 ReportsView::ReportsView(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) :
0036     KMyMoneyPlugin::Plugin(parent, metaData, args),
0037     m_view(nullptr)
0038 {
0039     // For information, announce that we have been loaded.
0040     qDebug("Plugins: reportsview loaded");
0041 }
0042 
0043 ReportsView::~ReportsView()
0044 {
0045     qDebug("Plugins: reportsview unloaded");
0046 }
0047 
0048 void ReportsView::plug(KXMLGUIFactory* guiFactory)
0049 {
0050     Q_UNUSED(guiFactory)
0051     m_view = new KReportsView;
0052     viewInterface()->addView(m_view, i18n("Reports"), View::Reports, Icons::Icon::Reports);
0053 }
0054 
0055 void ReportsView::unplug()
0056 {
0057     viewInterface()->removeView(View::Reports);
0058 }
0059 
0060 QVariant ReportsView::requestData(const QString &arg, uint type)
0061 {
0062     switch(type) {
0063     case eWidgetPlugin::WidgetType::NetWorthForecast:
0064         return QVariant::fromValue(netWorthForecast());
0065     case eWidgetPlugin::WidgetType::NetWorthForecastWithArgs:
0066         return QVariant::fromValue(netWorthForecast(arg));
0067     case eWidgetPlugin::WidgetType::Budget:
0068         return QVariant(budget());
0069     default:
0070         return QVariant();
0071     }
0072 
0073 }
0074 
0075 QWidget *ReportsView::netWorthForecast() const
0076 {
0077     MyMoneyReport reportCfg = MyMoneyReport(
0078                                   eMyMoney::Report::RowType::AssetLiability,
0079                                   static_cast<unsigned>(eMyMoney::Report::ColumnType::Months),
0080                                   eMyMoney::TransactionFilter::Date::UserDefined, // overridden by the setDateFilter() call below
0081                                   eMyMoney::Report::DetailLevel::Total,
0082                                   i18n("Net Worth Forecast"),
0083                                   i18n("Generated Report"));
0084 
0085     reportCfg.setChartByDefault(true);
0086     reportCfg.setChartCHGridLines(false);
0087     reportCfg.setChartSVGridLines(false);
0088     reportCfg.setChartDataLabels(false);
0089     reportCfg.setChartType(eMyMoney::Report::ChartType::Line);
0090     reportCfg.setIncludingSchedules(false);
0091     reportCfg.addAccountGroup(eMyMoney::Account::Type::Asset);
0092     reportCfg.addAccountGroup(eMyMoney::Account::Type::Liability);
0093     reportCfg.setColumnsAreDays(true);
0094     reportCfg.setConvertCurrency(true);
0095     reportCfg.setIncludingForecast(true);
0096     reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(+ 90));
0097     reports::PivotTable table(reportCfg);
0098 
0099     auto chartWidget = new reports::KReportChartView(nullptr);
0100 
0101     table.drawChart(*chartWidget);
0102     return chartWidget;
0103 }
0104 
0105 QWidget *ReportsView::netWorthForecast(const QString &arg) const
0106 {
0107     const QStringList liArgs = arg.split(';');
0108     if (liArgs.count() != 4)
0109         return new QWidget();
0110 
0111     eMyMoney::Report::DetailLevel detailLevel[4] = { eMyMoney::Report::DetailLevel::All, eMyMoney::Report::DetailLevel::Top, eMyMoney::Report::DetailLevel::Group, eMyMoney::Report::DetailLevel::Total, };
0112 
0113     MyMoneyReport reportCfg = MyMoneyReport(
0114                                   eMyMoney::Report::RowType::AssetLiability,
0115                                   static_cast<unsigned>(eMyMoney::Report::ColumnType::Months),
0116                                   eMyMoney::TransactionFilter::Date::UserDefined, // overridden by the setDateFilter() call below
0117                                   detailLevel[liArgs.at(0).toInt()],
0118                                   i18n("Net Worth Forecast"),
0119                                   i18n("Generated Report"));
0120 
0121     reportCfg.setChartByDefault(true);
0122     reportCfg.setChartCHGridLines(false);
0123     reportCfg.setChartSVGridLines(false);
0124     reportCfg.setChartType(eMyMoney::Report::ChartType::Line);
0125     reportCfg.setIncludingSchedules(false);
0126     reportCfg.setColumnsAreDays( true );
0127     reportCfg.setChartDataLabels(false);
0128     reportCfg.setConvertCurrency(true);
0129     reportCfg.setIncludingForecast(true);
0130     reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(liArgs.at(2).toLongLong()));
0131     reports::PivotTable table(reportCfg);
0132 
0133     auto forecastChart = new reports::KReportChartView(nullptr);
0134     forecastChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0135     table.drawChart(*forecastChart);
0136 
0137     // Adjust the size
0138     forecastChart->resize(liArgs.at(2).toInt() - 10, liArgs.at(3).toInt());
0139     forecastChart->show();
0140     forecastChart->update();
0141     return forecastChart;
0142 }
0143 
0144 QString ReportsView::budget() const
0145 {
0146     const auto file = MyMoneyFile::instance();
0147 
0148     QString html;
0149 
0150     // header
0151     html +=
0152         "<table width=\"97%\" align=\"center\" class=\"displayblock\" >"
0153         "<tr><td class=\"summaryheader\">"
0154         + i18n("Budget") + "</td></tr>";
0155 
0156     if (file->countBudgets() == 0) {
0157         html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
0158         html += "<tr>";
0159         html += QString("<td><center>%1</center></td>").arg(i18n("You have no budgets to display."));
0160         html += "</tr>";
0161         html += "</table></td></tr>";
0162         html += "</table>";
0163         return html;
0164     }
0165 
0166     auto prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction());
0167     bool isOverrun = false;
0168     int i = 0;
0169 
0170     //config report just like "Monthly Budgeted vs Actual
0171     MyMoneyReport reportCfg = MyMoneyReport(
0172                                   eMyMoney::Report::RowType::BudgetActual,
0173                                   static_cast<unsigned>(eMyMoney::Report::ColumnType::Months),
0174                                   eMyMoney::TransactionFilter::Date::CurrentMonth,
0175                                   eMyMoney::Report::DetailLevel::All,
0176                                   i18n("Monthly Budgeted vs. Actual"),
0177                                   i18n("Generated Report"));
0178 
0179     reportCfg.setBudget("Any", true);
0180 
0181     reports::PivotTable table(reportCfg);
0182 
0183     reports::PivotGrid grid = table.grid();
0184 
0185     //display budget summary
0186     html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
0187     html += "<tr class=\"itemtitle\">";
0188     html += "<td class=\"left\" colspan=\"3\">";
0189     html += i18n("Current Month Summary");
0190     html += "</td></tr>";
0191     html += "<tr class=\"item\">";
0192     html += "<td class=\"right\" width=\"60%\">";
0193     html += i18n("Budgeted");
0194     html += "</td>";
0195     html += "<td class=\"right\" width=\"20%\">";
0196     html += i18n("Actual");
0197     html += "</td>";
0198     html += "<td class=\"right\" width=\"20%\">";
0199     html += i18n("Difference");
0200     html += "</td></tr>";
0201 
0202     html += QString("<tr class=\"row-odd\">");
0203 
0204     MyMoneyMoney totalBudgetValue = grid.m_total[reports::eBudget].m_total;
0205     MyMoneyMoney totalActualValue = grid.m_total[reports::eActual].m_total;
0206     MyMoneyMoney totalBudgetDiffValue = grid.m_total[reports::eBudgetDiff].m_total;
0207 
0208     QString totalBudgetAmount = totalBudgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
0209     QString totalActualAmount = totalActualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
0210     QString totalBudgetDiffAmount = totalBudgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
0211 
0212     html += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(totalBudgetAmount, totalBudgetValue.isNegative()));
0213     html += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(totalActualAmount, totalActualValue.isNegative()));
0214     html += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(totalBudgetDiffAmount, totalBudgetDiffValue.isNegative()));
0215     html += "</tr>";
0216     html += "</table></td></tr>";
0217 
0218     //budget overrun
0219     html += "<tr class=\"gap\"><td>&nbsp;\n</td></tr>";
0220     html += "<tr><td><table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >";
0221     html += "<tr class=\"itemtitle\">";
0222     html += "<td class=\"left\" colspan=\"4\">";
0223     html += i18n("Budget Overruns");
0224     html += "</td></tr>";
0225     html += "<tr class=\"item\">";
0226     html += "<td class=\"left\" width=\"40%\">";
0227     html += i18n("Account");
0228     html += "</td>";
0229     html += "<td class=\"right\" width=\"20%\">";
0230     html += i18n("Budgeted");
0231     html += "</td>";
0232     html += "<td class=\"right\" width=\"20%\">";
0233     html += i18n("Actual");
0234     html += "</td>";
0235     html += "<td class=\"right\" width=\"20%\">";
0236     html += i18n("Difference");
0237     html += "</td></tr>";
0238 
0239     reports::PivotGrid::iterator it_outergroup = grid.begin();
0240     const int column = 0;
0241     while (it_outergroup != grid.end()) {
0242         i = 0;
0243         reports::PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin();
0244         while (it_innergroup != (*it_outergroup).end()) {
0245             reports::PivotInnerGroup::iterator it_row = (*it_innergroup).begin();
0246             while (it_row != (*it_innergroup).end()) {
0247                 //column number is 1 because the report includes only current month
0248                 if (it_row.value()[reports::eBudgetDiff].value(column).isNegative()) {
0249                     //get report account to get the name later
0250                     reports::ReportAccount rowname = it_row.key();
0251 
0252                     //write the outergroup if it is the first row of outergroup being shown
0253                     if (i == 0) {
0254                         html += "<tr style=\"font-weight:bold;\">";
0255                         html += QString("<td class=\"left\" colspan=\"4\">%1</td>").arg(MyMoneyAccount::accountTypeToString(rowname.accountType()));
0256                         html += "</tr>";
0257                     }
0258                     html += QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd");
0259 
0260                     //get values from grid
0261                     MyMoneyMoney actualValue = it_row.value()[reports::eActual][column];
0262                     MyMoneyMoney budgetValue = it_row.value()[reports::eBudget][column];
0263                     MyMoneyMoney budgetDiffValue = it_row.value()[reports::eBudgetDiff][column];
0264 
0265                     //format amounts
0266                     QString actualAmount = actualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
0267                     QString budgetAmount = budgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
0268                     QString budgetDiffAmount = budgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec);
0269 
0270                     //account name
0271                     html += QString("<td>") + link(VIEW_LEDGER, QString("?id=%1").arg(rowname.id()), QString()) + rowname.name() + linkend() + "</td>";
0272 
0273                     //show amounts
0274                     html += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(budgetAmount, budgetValue.isNegative()));
0275                     html += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(actualAmount, actualValue.isNegative()));
0276                     html += QString("<td class=\"right nowrap\">%1</td>").arg(showColoredAmount(budgetDiffAmount, budgetDiffValue.isNegative()));
0277                     html += "</tr>";
0278 
0279                     //set the flag that there are overruns
0280                     isOverrun = true;
0281                 }
0282                 ++it_row;
0283             }
0284             ++it_innergroup;
0285         }
0286         ++it_outergroup;
0287     }
0288 
0289     //if no negative differences are found, then inform that
0290     if (isOverrun == false) {
0291         html += QString::fromLatin1("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(((i++ & 1) == 1) ? QLatin1String("even") : QLatin1String("odd"));
0292         html += QString::fromLatin1("<td class=\"center\" colspan=\"4\">%1</td>").arg(i18n("No Budget Categories have been overrun"));
0293         html += "</tr>";
0294     }
0295     html += "</table></td></tr>";
0296     html += "</table>";
0297     return html;
0298 }
0299 
0300 QString ReportsView::showColoredAmount(const QString &amount, bool isNegative) const
0301 {
0302     if (isNegative) {
0303         //if negative, get the settings for negative numbers
0304         return QString("<font color=\"%1\">%2</font>").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name(), amount);
0305     }
0306 
0307     //if positive, return the same string
0308     return amount;
0309 }
0310 
0311 QString ReportsView::link(const QString& view, const QString& query, const QString& _title) const
0312 {
0313     QString titlePart;
0314     QString title(_title);
0315     if (!title.isEmpty())
0316         titlePart = QString(" title=\"%1\"").arg(title.replace(QLatin1Char(' '), "&nbsp;"));
0317 
0318     return QString("<a href=\"/%1%2\"%3>").arg(view, query, titlePart);
0319 }
0320 
0321 QString ReportsView::linkend() const
0322 {
0323     return QStringLiteral("</a>");
0324 }
0325 
0326 K_PLUGIN_CLASS_WITH_JSON(ReportsView, "reportsview.json")
0327 
0328 #include "reportsview.moc"