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> \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(' '), " ")); 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"