File indexing completed on 2024-04-28 16:30:13

0001 /***************************************************************************
0002  * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr
0003  * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  ***************************************************************************/
0006 /** @file
0007  * A skrooge plugin for monthly report.
0008  *
0009  * @author Stephane MANKOWSKI
0010  */
0011 #include "skgreportbank.h"
0012 
0013 #include <qdir.h>
0014 #include <qstandardpaths.h>
0015 #include <qurl.h>
0016 
0017 #include <kcolorscheme.h>
0018 
0019 #include "skgaccountobject.h"
0020 #include "skgdocumentbank.h"
0021 #include "skgoperationobject.h"
0022 #include "skgrecurrentoperationobject.h"
0023 #include "skgruleobject.h"
0024 #include "skgtraces.h"
0025 #include "skgunitobject.h"
0026 
0027 SKGReportBank::SKGReportBank(SKGDocument* iDocument) : SKGReport(iDocument)
0028 {
0029     SKGTRACEINFUNC(1)
0030     connect(this, &SKGReportBank::changed, this, &SKGReportBank::changed2);
0031 }
0032 
0033 SKGReportBank::~SKGReportBank()
0034 {
0035     SKGTRACEINFUNC(1)
0036 }
0037 
0038 QVariantList SKGReportBank::getAlarms()
0039 {
0040     QString cacheId = QStringLiteral("getAlarms");
0041     QVariantList table = m_cache.value(cacheId).toList();
0042     if (table.isEmpty()) {
0043         SKGTRACEINFUNC(10)
0044         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0045         if (doc != nullptr) {
0046             SKGServices::SKGUnitInfo primary = doc->getPrimaryUnit();
0047 
0048             SKGObjectBase::SKGListSKGObjectBase rules;
0049             SKGError err = doc->getObjects(QStringLiteral("v_rule"), QStringLiteral("t_action_type='A' ORDER BY i_ORDER"), rules);
0050             int nb = rules.count();
0051             if (nb != 0) {
0052                 for (int i = 0; !err && i < nb; ++i) {
0053                     SKGRuleObject rule(rules.at(i));
0054                     SKGRuleObject::SKGAlarmInfo alarm = rule.getAlarmInfo();
0055 
0056                     QVariantList item;  // clazy:exclude=container-inside-loop
0057                     // Build the message
0058                     if (alarm.Message.contains(QLatin1String("%3"))) {
0059                         alarm.Message = alarm.Message.arg(doc->formatMoney(alarm.Amount, primary, false), doc->formatMoney(alarm.Limit, primary, false), doc->formatMoney(alarm.Amount - alarm.Limit, primary, false));
0060                     } else if (alarm.Message.contains(QLatin1String("%2"))) {
0061                         alarm.Message = alarm.Message.arg(doc->formatMoney(alarm.Amount, primary, false), doc->formatMoney(alarm.Limit, primary, false));
0062                     } else if (alarm.Message.contains(QLatin1String("%1"))) {
0063                         alarm.Message = alarm.Message.arg(doc->formatMoney(alarm.Amount, primary, false));
0064                     }
0065                     item.push_back(alarm.Message);
0066                     item.push_back(alarm.Amount);
0067                     item.push_back(alarm.Limit);
0068                     item.push_back(alarm.Amount - alarm.Limit);
0069                     item.push_back(alarm.Raised);
0070 
0071                     table.push_back(item);
0072                 }
0073             }
0074             m_cache[cacheId] = table;
0075         }
0076     }
0077     return table;
0078 }
0079 
0080 QVariantList SKGReportBank::getInterests()
0081 {
0082     QString cacheId = QStringLiteral("getInterests");
0083     QVariantList table = m_cache.value(cacheId).toList();
0084     if (table.isEmpty()) {
0085         SKGTRACEINFUNC(10)
0086         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0087         if (doc != nullptr) {
0088             // Build display
0089             int year = SKGServices::periodToDate(getPeriod()).year();
0090             SKGObjectBase::SKGListSKGObjectBase objs;
0091             auto sqlFilter = getSqlFilter();
0092             if (sqlFilter.isEmpty()) {
0093                 sqlFilter = QStringLiteral("1=1");
0094             }
0095             SKGError err = doc->getObjects(QStringLiteral("v_account"), "(" % sqlFilter % QStringLiteral(") AND t_close='N' AND EXISTS(select 1 from interest where interest.rd_account_id=v_account.id) ORDER BY t_name"), objs);
0096             IFOK(err) {
0097                 int nb = objs.count();
0098                 table.reserve(nb + 2);
0099                 if (nb != 0) {
0100                     {
0101                         // Add header
0102                         QVariantList item;
0103                         item.push_back(false);
0104                         item.push_back(i18nc("Title", "Account"));
0105                         item.push_back(year);
0106                         table.push_back(item);
0107                     }
0108 
0109                     // Add items
0110                     double sum = 0;
0111                     for (int i = 0; i < nb; ++i) {
0112                         SKGAccountObject obj(objs.at(i));
0113 
0114                         SKGAccountObject::SKGInterestItemList oInterestList;
0115                         double oInterests = 0;
0116                         obj.getInterestItems(oInterestList, oInterests, year);
0117                         sum += oInterests;
0118 
0119                         QVariantList item;  // clazy:exclude=container-inside-loop
0120                         item.push_back(false);
0121                         item.push_back(obj.getName());
0122                         item.push_back(oInterests);
0123                         table.push_back(item);
0124                     }
0125                     {
0126                         // Add sum
0127                         QVariantList item;
0128                         item.push_back(true);
0129                         item.push_back(i18nc("Noun, the numerical total of a sum of values", "Total"));
0130                         item.push_back(sum);
0131                         table.push_back(item);
0132                     }
0133                 }
0134             }
0135 
0136             m_cache[cacheId] = table;
0137         }
0138     }
0139     return table;
0140 }
0141 
0142 QVariantList SKGReportBank::getBudgetTable()
0143 {
0144     QString cacheId = QStringLiteral("getBudgetTable");
0145     QVariantList table = m_cache.value(cacheId).toList();
0146     if (table.isEmpty()) {
0147         SKGTRACEINFUNC(10)
0148         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0149         table = doc != nullptr ? doc->getBudget(getPeriod()) : QVariantList();
0150         m_cache[cacheId] = table;
0151     }
0152     return table;
0153 }
0154 
0155 QVariantList SKGReportBank::getPortfolio()
0156 {
0157     QString cacheId = QStringLiteral("getPortfolio");
0158     QVariantList table = m_cache.value(cacheId).toList();
0159     if (table.isEmpty()) {
0160         SKGTRACEINFUNC(10)
0161         QString period = getPeriod();
0162         if (!period.isEmpty()) {
0163             QDate date = qMin(SKGServices::periodToDate(period), QDate::currentDate().addDays(1 - QDate::currentDate().day()).addMonths(1).addDays(-1));
0164             auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0165             if (doc != nullptr) {
0166                 SKGServices::SKGUnitInfo primary = doc->getPrimaryUnit();
0167 
0168                 // Get list of transactions
0169                 SKGObjectBase::SKGListSKGObjectBase objs;
0170                 auto sqlFilter = getSqlFilter();
0171                 if (sqlFilter.isEmpty()) {
0172                     sqlFilter = QStringLiteral("1=1");
0173                 }
0174                 SKGError err = doc->getObjects(QStringLiteral("v_operation_display"), "d_date<'" % SKGServices::dateToSqlString(date) % "' AND rc_unit_id IN (SELECT id FROM v_unit_display WHERE t_type='S' AND (" % sqlFilter % ") AND f_QUANTITYOWNED>0.01) ORDER BY t_UNIT", objs);
0175                 int nb = objs.count();
0176                 if (!err && nb > 0) {
0177                     table.reserve(nb + 1);
0178 
0179                     QVariantList line;
0180                     line << doc->getDisplay(QStringLiteral("t_symbol"))
0181                          << doc->getDisplay(QStringLiteral("t_UNIT"))
0182                          << i18nc("Column table title", "Quantity")
0183                          << i18nc("Column table title", "Purchase amount")
0184                          << i18nc("Column table title", "Initial amount")
0185                          << QLocale().toString(date, QLocale::ShortFormat)
0186                          << i18nc("Column table title", "Variation")
0187                          << i18nc("Column table title", "Variation %");
0188                     table << QVariant(line);
0189 
0190                     QVector<unitValues> listUnitValues;
0191                     unitValues current;
0192                     current.initalAmount = 0.0;
0193                     current.purchaseAmount = 0.0;
0194                     current.currentAmount = 0.0;
0195                     current.quantity = 0.0;
0196                     listUnitValues.reserve(nb);
0197                     for (int i = 0; i < nb; ++i) {
0198                         SKGOperationObject obj(objs.at(i));
0199                         SKGUnitObject unit;
0200                         obj.getUnit(unit);
0201                         if (i != 0 && current.unit != unit) {
0202                             listUnitValues.push_back(current);
0203                             current.initalAmount = 0.0;
0204                             current.purchaseAmount = 0.0;
0205                             current.currentAmount = 0.0;
0206                             current.quantity = 0.0;
0207                         }
0208 
0209                         current.unit = unit;
0210                         current.initalAmount += obj.getAmount(obj.getDate());
0211                         current.currentAmount += obj.getAmount(date);
0212                         SKGObjectBase::SKGListSKGObjectBase oGroupedOperations;
0213                         obj.getGroupedOperations(oGroupedOperations);
0214                         oGroupedOperations.removeAll(obj);
0215                         if (oGroupedOperations.count() == 1) {
0216                             SKGOperationObject obj2(oGroupedOperations.at(0));
0217                             current.purchaseAmount += obj2.getAmount(obj.getDate());
0218                         }
0219                         current.quantity += SKGServices::stringToDouble(obj.getAttribute(QStringLiteral("f_QUANTITY")));
0220                     }
0221                     if (!current.unit.getName().isEmpty()) {
0222                         listUnitValues.push_back(current);
0223                     }
0224 
0225                     nb = listUnitValues.count();
0226                     for (int j = 0; j < nb; ++j) {
0227                         unitValues current2 = listUnitValues.at(j);
0228 
0229                         SKGServices::SKGUnitInfo ui = current2.unit.getUnitInfo();
0230                         ui.Value = 1;
0231 
0232                         QVariantList line2;  // clazy:exclude=container-inside-loop
0233                         line2 << current2.unit.getSymbol()
0234                               << current2.unit.getName()
0235                               << doc->formatMoney(current2.quantity, ui, false)
0236                               << current2.purchaseAmount
0237                               << current2.initalAmount
0238                               << current2.currentAmount
0239                               << current2.currentAmount - current2.initalAmount
0240                               << (current2.initalAmount == 0.0 ? 0.0 : 100.0 * (current2.currentAmount - current2.initalAmount) / current2.initalAmount);
0241                         table << QVariant(line2);
0242                     }
0243                 }
0244             }
0245         }
0246         m_cache[cacheId] = table;
0247     }
0248 
0249     return table;
0250 }
0251 
0252 QVariantList SKGReportBank::getUnitTable()
0253 {
0254     QString cacheId = QStringLiteral("getUnitTable");
0255     QVariantList table = m_cache.value(cacheId).toList();
0256     if (table.isEmpty()) {
0257         SKGTRACEINFUNC(10)
0258         QString period = getPeriod();
0259         if (!period.isEmpty()) {
0260             QDate date1 = SKGServices::periodToDate(getPreviousPeriod());
0261             QDate date2 = SKGServices::periodToDate(period);
0262 
0263             auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0264             if (doc != nullptr) {
0265                 SKGServices::SKGUnitInfo primary = doc->getPrimaryUnit();
0266 
0267                 SKGObjectBase::SKGListSKGObjectBase units;
0268                 SKGError err = doc->getObjects(QStringLiteral("v_unit_display"), QStringLiteral("1=1 ORDER BY t_TYPENLS"), units);
0269                 int nbUnits = units.count();
0270                 if (nbUnits != 0) {
0271                     table.reserve(nbUnits + 1);
0272                     QVariantList line;
0273                     line << "sum"
0274                          << doc->getDisplay(QStringLiteral("t_UNIT"))
0275                          << QLocale().toString(date1, QLocale::ShortFormat)
0276                          << QLocale().toString(date2, QLocale::ShortFormat)
0277                          << "%"
0278                          << doc->getDisplay(QStringLiteral("t_symbol"));
0279                     table << QVariant(line);
0280                     for (const auto& item : qAsConst(units)) {
0281                         SKGUnitObject unit(item);
0282                         double v1 = unit.getAmount(date1);
0283                         double v2 = unit.getAmount(date2);
0284                         QVariantList line2;  // clazy:exclude=container-inside-loop
0285                         line2 << false << unit.getName() << v1 << v2 << (100.0 * (v2 - v1) / qAbs(v1)) << unit.getSymbol();
0286                         table << QVariant(line2);
0287                     }
0288                 }
0289             }
0290         }
0291         m_cache[cacheId] = table;
0292     }
0293 
0294     return table;
0295 }
0296 
0297 QVariantList SKGReportBank::getAccountTable()
0298 {
0299     QString cacheId = QStringLiteral("getAccountTable");
0300     QVariantList table = m_cache.value(cacheId).toList();
0301     if (table.isEmpty()) {
0302         SKGTRACEINFUNC(10)
0303         QString period = getPeriod();
0304         if (!period.isEmpty()) {
0305             QDate date1 = SKGServices::periodToDate(getPreviousPeriod());
0306             QDate date2 = SKGServices::periodToDate(period);
0307             QDate date3 = date2.addYears(-1);
0308             if (date3 == date1) {
0309                 date3 = date3.addYears(-1);
0310             }
0311 
0312             auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0313             if (doc != nullptr) {
0314                 SKGServices::SKGUnitInfo primary = doc->getPrimaryUnit();
0315 
0316                 SKGObjectBase::SKGListSKGObjectBase accounts;
0317                 auto sqlFilter = getSqlFilter();
0318                 if (sqlFilter.isEmpty()) {
0319                     sqlFilter = QStringLiteral("1=1");
0320                 }
0321                 SKGError err = doc->getObjects(QStringLiteral("v_account"), sqlFilter + QStringLiteral(" ORDER BY t_TYPENLS, t_BANK, t_name"), accounts);
0322                 IFOK(err) {
0323                     table.push_back(QVariantList() << "sum" << doc->getDisplay(QStringLiteral("t_ACCOUNT")) << QLocale().toString(date1, QLocale::ShortFormat) << QLocale().toString(date2, QLocale::ShortFormat) <<
0324                                     "%" << QLocale().toString(date3, QLocale::ShortFormat) << QLocale().toString(date2, QLocale::ShortFormat) << "%");
0325                     double sumTypeV1 = 0;
0326                     double sumTypeV2 = 0;
0327                     double sumTypeV3 = 0;
0328                     double sumV1 = 0;
0329                     double sumV2 = 0;
0330                     double sumV3 = 0;
0331                     int nbAdded = 0;
0332                     QString currentType;
0333                     int nb = accounts.count();
0334                     for (int i = 0; !err && i < nb; ++i) {
0335                         SKGAccountObject account(accounts.at(i));
0336                         double v1 = account.getAmount(date1);
0337                         double v2 = account.getAmount(date2);
0338                         double v3 = account.getAmount(date3);
0339                         QString type = account.getAttribute(QStringLiteral("t_TYPENLS"));
0340                         bool closed = account.isClosed();
0341                         if (type != currentType) {
0342                             if (!currentType.isEmpty() && nbAdded > 0) {
0343                                 table.push_back(QVariantList() << true << i18nc("Noun",  "Total of %1", currentType) << sumTypeV1 << sumTypeV2 <<
0344                                                 (100.0 * (sumTypeV2 - sumTypeV1) / qAbs(sumTypeV1)) << sumTypeV3 << sumTypeV2
0345                                                 << (100.0 * (sumTypeV2 - sumTypeV3) / qAbs(sumTypeV3))
0346                                                 << "" << "");
0347                                 sumTypeV1 = 0;
0348                                 sumTypeV2 = 0;
0349                                 sumTypeV3 = 0;
0350                                 nbAdded = 0;
0351                             }
0352                             currentType = type;
0353                         }
0354                         if (!closed || qAbs(v1) > 0.01 || qAbs(v2) > 0.01 || qAbs(v3) > 0.01) {
0355                             QString icon = account.getAttribute(QStringLiteral("t_ICON"));
0356                             if (!icon.isEmpty()) {
0357                                 QString iconfile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "skrooge/images/logo/" % icon);
0358                                 if (!iconfile.isEmpty()) {
0359                                     icon = iconfile;
0360                                 }
0361                             }
0362                             table.push_back(QVariantList() << false << account.getName() << v1 << v2 << (100.0 * (v2 - v1) / qAbs(v1)) << v3 << v2
0363                                             << (100.0 * (v2 - v3) / qAbs(v3)) << account.getAttribute(QStringLiteral("t_BANK")) << icon);
0364                             nbAdded++;
0365                         }
0366                         sumTypeV1 += v1;
0367                         sumTypeV2 += v2;
0368                         sumTypeV3 += v3;
0369                         sumV1 += v1;
0370                         sumV2 += v2;
0371                         sumV3 += v3;
0372                     }
0373 
0374                     table.push_back(QVariantList() << true << i18nc("Noun",  "Total of %1", currentType) << sumTypeV1 << sumTypeV2
0375                                     << (100.0 * (sumTypeV2 - sumTypeV1) / qAbs(sumTypeV1)) << sumTypeV3 << sumTypeV2 << (100.0 * (sumTypeV2 - sumTypeV3) / qAbs(sumTypeV3))
0376                                     << "" << "");
0377 
0378                     table.push_back(QVariantList() << true << i18nc("Noun, the numerical total of a sum of values", "Total") << sumV1 << sumV2
0379                                     << (100.0 * (sumV2 - sumV1) / qAbs(sumV1)) << sumV3 << sumV2 << (100.0 * (sumV2 - sumV3) / qAbs(sumV3))
0380                                     << "" << "");
0381                 }
0382             }
0383         }
0384         m_cache[cacheId] = table;
0385     }
0386     return table;
0387 }
0388 
0389 QVariantList SKGReportBank::getBankTable()
0390 {
0391     QString cacheId = QStringLiteral("getBankTable");
0392     QVariantList table = m_cache.value(cacheId).toList();
0393     if (table.isEmpty()) {
0394         SKGTRACEINFUNC(10)
0395         QString period = getPeriod();
0396         if (!period.isEmpty()) {
0397             QDate date1 = SKGServices::periodToDate(getPreviousPeriod());
0398             QDate date2 = SKGServices::periodToDate(period);
0399             QDate date3 = date2.addYears(-1);
0400             if (date3 == date1) {
0401                 date3 = date3.addYears(-1);
0402             }
0403             auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0404             if (doc != nullptr) {
0405                 SKGServices::SKGUnitInfo primary = doc->getPrimaryUnit();
0406 
0407                 SKGObjectBase::SKGListSKGObjectBase accounts;
0408                 auto sqlFilter = getSqlFilter();
0409                 if (sqlFilter.isEmpty()) {
0410                     sqlFilter = QStringLiteral("1=1");
0411                 }
0412                 SKGError err = doc->getObjects(QStringLiteral("v_account"), sqlFilter + QStringLiteral(" ORDER BY t_BANK"), accounts);
0413                 IFOK(err) {
0414                     table.push_back(QVariantList() << "sum" << doc->getDisplay(QStringLiteral("t_BANK")) << QLocale().toString(date1, QLocale::ShortFormat) << QLocale().toString(date2, QLocale::ShortFormat) <<
0415                                     "%" << QLocale().toString(date3, QLocale::ShortFormat) << QLocale().toString(date2, QLocale::ShortFormat) << "%");
0416                     double sumTypeV1 = 0;
0417                     double sumTypeV2 = 0;
0418                     double sumTypeV3 = 0;
0419                     double sumV1 = 0;
0420                     double sumV2 = 0;
0421                     double sumV3 = 0;
0422                     QString currentName;
0423                     QString currentIcon;
0424                     bool currentOpen = false;
0425                     int nb = accounts.count();
0426                     for (int i = 0; !err && i < nb; ++i) {
0427                         SKGAccountObject account(accounts.at(i));
0428                         double v1 = account.getAmount(date1);
0429                         double v2 = account.getAmount(date2);
0430                         double v3 = account.getAmount(date3);
0431                         QString name = account.getAttribute(QStringLiteral("t_BANK"));
0432                         QString icon = account.getAttribute(QStringLiteral("t_ICON"));
0433                         if (!icon.isEmpty()) {
0434                             QString iconfile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "skrooge/images/logo/" % icon);
0435                             if (!iconfile.isEmpty()) {
0436                                 icon = iconfile;
0437                             }
0438                         }
0439                         bool open = !account.isClosed();
0440                         if (name != currentName) {
0441                             if (!currentName.isEmpty() && currentOpen) {
0442                                 table.push_back(QVariantList() << false << currentName << sumTypeV1 << sumTypeV2 <<
0443                                                 (100.0 * (sumTypeV2 - sumTypeV1) / qAbs(sumTypeV1)) << sumTypeV3 << sumTypeV2 << (100.0 * (sumTypeV2 - sumTypeV3) / qAbs(sumTypeV3)) <<
0444                                                 currentIcon);
0445                                 sumTypeV1 = 0;
0446                                 sumTypeV2 = 0;
0447                                 sumTypeV3 = 0;
0448                                 currentOpen = open;
0449                             }
0450                             currentName = name;
0451                             currentIcon = icon;
0452                         }
0453                         currentOpen = currentOpen || open;
0454 
0455                         sumTypeV1 += v1;
0456                         sumTypeV2 += v2;
0457                         sumTypeV3 += v3;
0458                         sumV1 += v1;
0459                         sumV2 += v2;
0460                         sumV3 += v3;
0461                     }
0462 
0463                     if (currentOpen) {
0464                         table.push_back(QVariantList() << false << currentName << sumTypeV1 << sumTypeV2
0465                                         << (100.0 * (sumTypeV2 - sumTypeV1) / qAbs(sumTypeV1)) << sumTypeV3
0466                                         << sumTypeV2 << (100.0 * (sumTypeV2 - sumTypeV3) / qAbs(sumTypeV3))
0467                                         << currentIcon);
0468                     }
0469 
0470                     table.push_back(QVariantList() << true << i18nc("Noun, the numerical total of a sum of values", "Total") << sumV1 << sumV2 << (100.0 * (sumV2 - sumV1) / qAbs(sumV1)) << sumV3 << sumV2 << (100.0 * (sumV2 - sumV3) / qAbs(sumV3)));
0471                 }
0472             }
0473         }
0474         m_cache[cacheId] = table;
0475     }
0476     return table;
0477 }
0478 
0479 QVariantList SKGReportBank::getScheduledOperations()
0480 {
0481     QString cacheId = QStringLiteral("getScheduledOperations");
0482     QVariantList table = m_cache.value(cacheId).toList();
0483     if (table.isEmpty()) {
0484         SKGTRACEINFUNC(10)
0485         SKGObjectBase::SKGListSKGObjectBase objs;
0486         auto scheduled_operation_days_max = m_parameters.value(QStringLiteral("scheduled_operation_days_max"), QStringLiteral("30")).toString();
0487         SKGError err = m_document->getObjects(QStringLiteral("v_recurrentoperation_display"),
0488                                               QStringLiteral("i_nb_times!=0 AND d_date<=date('now', 'localtime','+") + scheduled_operation_days_max + " day') ORDER BY d_date", objs);
0489         QDate d = QDate::currentDate().addDays(SKGServices::stringToInt(scheduled_operation_days_max));
0490         QString dateFormatShort = QLocale().dateFormat(QLocale::ShortFormat);
0491         IFOK(err) {
0492             int nb = objs.count();
0493             if (nb != 0) {
0494                 table.reserve(nb);
0495                 for (int i = 0; i < nb; ++i) {
0496                     SKGRecurrentOperationObject obj(objs.at(i));
0497                     bool first = true;
0498                     auto obj_date = obj.getDate().toString(dateFormatShort);
0499                     while (true) {
0500                         if (obj.getDate() > d || (obj.hasTimeLimit() && obj.getTimeLimit() == 0)) {
0501                             break;
0502                         } else {
0503                             bool bold = false;
0504                             if (obj.isWarnEnabled() &&  QDate::currentDate() >= obj.getDate().addDays(-obj.getWarnDays())) {
0505                                 bold = true;
0506                             }
0507                             auto name = obj.getDisplayName();
0508                             if (!first) {
0509                                 name = name.replace(obj_date, obj.getDate().toString(dateFormatShort));
0510                             }
0511                             table.push_back(QVariantList() << bold << name << (first ? obj.getUniqueID() : QString()) << obj.getDate());
0512                             first = false;
0513                             obj.setDate(obj.getNextDate());
0514                             if (obj.hasTimeLimit()) {
0515                                 obj.setTimeLimit(obj.getTimeLimit() - 1);
0516                             }
0517                         }
0518                     }
0519                 }
0520             }
0521             std::sort(table.begin(), table.end(), [](const QVariant & v1, const QVariant & v2) {
0522                 return v1.toList().at(3).toDate() < v2.toList().at(3).toDate();
0523             });
0524             m_cache[cacheId] = table;
0525         }
0526     }
0527     return table;
0528 }
0529 
0530 QVariantList SKGReportBank::getMainCategoriesForPeriod()
0531 {
0532     QString cacheId = QStringLiteral("getMainCategoriesForPeriod");
0533     QVariantList table = m_cache.value(cacheId).toList();
0534     if (table.isEmpty()) {
0535         SKGTRACEINFUNC(10)
0536         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0537         table = doc != nullptr ? doc->getMainCategories(getPeriod(), 5) : QVariantList();
0538         m_cache[cacheId] = table;
0539     }
0540     return table;
0541 }
0542 
0543 QVariantList SKGReportBank::getMainCategoriesForPreviousPeriod()
0544 {
0545     QString cacheId = QStringLiteral("getMainCategoriesForPreviousPeriod");
0546     QVariantList table = m_cache.value(cacheId).toList();
0547     if (table.isEmpty()) {
0548         SKGTRACEINFUNC(10)
0549         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0550         table = doc != nullptr ? doc->getMainCategories(getPreviousPeriod(), 5) : QVariantList();
0551         m_cache[cacheId] = table;
0552     }
0553     return table;
0554 }
0555 
0556 QStringList SKGReportBank::get5MainCategoriesVariation()
0557 {
0558     QString cacheId = QStringLiteral("get5MainCategoriesVariation");
0559     QStringList table = m_cache.value(cacheId).toStringList();
0560     if (table.isEmpty()) {
0561         SKGTRACEINFUNC(10)
0562         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0563         table = doc != nullptr ? doc->get5MainCategoriesVariationList(getPeriod(), getPreviousPeriod(), false) : QStringList();
0564         m_cache[cacheId] = table;
0565     }
0566     return table;
0567 }
0568 
0569 QStringList SKGReportBank::get5MainCategoriesVariationIssue()
0570 {
0571     QString cacheId = QStringLiteral("get5MainCategoriesVariationIssue");
0572     QStringList table = m_cache.value(cacheId).toStringList();
0573     if (table.isEmpty()) {
0574         SKGTRACEINFUNC(10)
0575         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0576         table = doc != nullptr ? doc->get5MainCategoriesVariationList(getPeriod(), getPreviousPeriod(), true) : QStringList();
0577         m_cache[cacheId] = table;
0578     }
0579     return table;
0580 }
0581 
0582 QVariantMap SKGReportBank::getPersonalFinanceScoreDetails(bool iTransfer, bool iTracker)
0583 {
0584     KColorScheme scheme(QPalette::Normal, KColorScheme::Window);
0585 
0586     QVariantMap output;
0587     double pfs = getPersonalFinanceScore(iTransfer, iTracker);
0588     output[QStringLiteral("value")] = pfs;
0589     if (pfs < 0) {
0590         output[QStringLiteral("level")] = QStringLiteral("danger");
0591         output[QStringLiteral("message")] = i18nc("An advice", "You must try to get out of debt.");
0592         output[QStringLiteral("color")] = scheme.foreground(KColorScheme::NegativeText).color().name().right(6);
0593     } else if (pfs >= 25) {
0594         output[QStringLiteral("level")] = QStringLiteral("success");
0595         output[QStringLiteral("message")] = i18nc("An advice", "Congratulations, you are now financially independent.");
0596         output[QStringLiteral("color")] = scheme.foreground(KColorScheme::PositiveText).color().name().right(6);
0597     } else if (pfs >= 10) {
0598         output[QStringLiteral("level")] = QStringLiteral("success");
0599         output[QStringLiteral("message")] = i18nc("An advice", "Congratulations, You saved up ten year’s worth of expenses.");
0600         output[QStringLiteral("color")] = scheme.foreground(KColorScheme::PositiveText).color().name().right(6);
0601     } else if (pfs >= 2) {
0602         output[QStringLiteral("level")] = QStringLiteral("warning");
0603         output[QStringLiteral("message")] = i18nc("An advice", "You saved up %1 year’s worth of expenses. You should continue your effort.", SKGServices::intToString(pfs));
0604         output[QStringLiteral("color")] = scheme.foreground(KColorScheme::NeutralText).color().name().right(6);
0605     } else if (pfs >= 1) {
0606         output[QStringLiteral("level")] = QStringLiteral("warning");
0607         output[QStringLiteral("message")] = i18nc("An advice", "You saved up one year’s worth of expenses. You should maintain your effort.");
0608         output[QStringLiteral("color")] = scheme.foreground(KColorScheme::NeutralText).color().name().right(6);
0609     } else {
0610         output[QStringLiteral("level")] = QStringLiteral("warning");
0611         output[QStringLiteral("message")] = i18nc("An advice", "You do not have debt but you have no margin. You must maintain your effort.");
0612         output[QStringLiteral("color")] = scheme.foreground(KColorScheme::NeutralText).color().name().right(6);
0613     }
0614     return output;
0615 }
0616 
0617 double SKGReportBank::getPersonalFinanceScore(bool iTransfer, bool iTracker)
0618 {
0619     double as = getAnnualSpending(iTransfer, iTracker);
0620     return (as == 0.0 ? 0.0 : getNetWorth(iTransfer, iTracker) / as);
0621 }
0622 
0623 double SKGReportBank::getAnnualSpending(bool iTransfer, bool iTracker)
0624 {
0625     QString cacheId = QStringLiteral("getAnnualSpending-") % (iTransfer ? QStringLiteral("Y") : QStringLiteral("N")) % (iTracker ? QStringLiteral("Y") : QStringLiteral("N"));
0626     double output = m_cache.value(cacheId).toDouble();
0627     if (!m_cache.contains(cacheId)) {
0628         SKGTRACEINFUNC(10)
0629         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0630         if (doc != nullptr) {
0631             QString result;
0632             QDate d2 = SKGServices::periodToDate(getPeriod());
0633             QDate d1 = d2.addYears(-1);
0634             doc->executeSingleSelectSqliteOrder("SELECT TOTAL(f_REALCURRENTAMOUNT) FROM v_suboperation_consolidated WHERE d_date BETWEEN '" %
0635                                                 SKGServices::dateToSqlString(d1) % "' AND '" % SKGServices::dateToSqlString(d2) %
0636                                                 "' AND t_TYPEEXPENSE='-'"
0637                                                 % (iTransfer ? "" : " AND t_TRANSFER='N'")
0638                                                 % (iTracker ? "" : " AND t_REFUND=''")
0639                                                 , result);
0640             output = -SKGServices::stringToDouble(result);
0641             m_cache[cacheId] = output;
0642         }
0643     }
0644     return output;
0645 }
0646 
0647 double SKGReportBank::getNetWorth(bool iTransfer, bool iTracker)
0648 {
0649     QString cacheId = QStringLiteral("getNetWorth-") % (iTransfer ? QStringLiteral("Y") : QStringLiteral("N")) % (iTracker ? QStringLiteral("Y") : QStringLiteral("N"));
0650     double output = m_cache.value(cacheId).toDouble();
0651     if (!m_cache.contains(cacheId)) {
0652         SKGTRACEINFUNC(10)
0653         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0654         if (doc != nullptr) {
0655             QString result;
0656             QDate d = SKGServices::periodToDate(getPeriod());
0657             doc->executeSingleSelectSqliteOrder("SELECT TOTAL(f_REALCURRENTAMOUNT) FROM v_suboperation_consolidated WHERE d_date<='" %
0658                                                 SKGServices::dateToSqlString(d) % "' "
0659                                                 % (iTransfer ? "" : " AND t_TRANSFER='N'")
0660                                                 % (iTracker ? "" : " AND t_REFUND=''")
0661                                                 , result);
0662             output = SKGServices::stringToDouble(result);
0663             m_cache[cacheId] = output;
0664         }
0665     }
0666     return output;
0667 }
0668 
0669 QVariantList SKGReportBank::getIncomeVsExpenditure(bool iOnSubOperation, bool iGrouped, bool iTransfer, bool iTracker, const QString& iWhereClause1, const QString& iWhereClause2)
0670 {
0671     QString cacheId = QStringLiteral("getIncomeVsExpenditure-") % (iOnSubOperation ? QStringLiteral("Y") : QStringLiteral("N")) % (iGrouped ? QStringLiteral("Y") : QStringLiteral("N")) % (iTransfer ? QStringLiteral("Y") : QStringLiteral("N")) % (iTracker ? QStringLiteral("Y") : QStringLiteral("N")) % iWhereClause1 % iWhereClause2;
0672     QVariantList table = m_cache.value(cacheId).toList();
0673     if (table.isEmpty()) {
0674         SKGTRACEINFUNC(10)
0675         auto* doc = qobject_cast<SKGDocumentBank*>(m_document);
0676         if (doc != nullptr) {
0677             QString tableDb = (iOnSubOperation ? QStringLiteral("v_suboperation_consolidated") : QStringLiteral("v_operation_display"));
0678             QString amount = (iOnSubOperation ? QStringLiteral("f_REALCURRENTAMOUNT") : QStringLiteral("f_CURRENTAMOUNT"));
0679 
0680             SKGStringListList listTmp;
0681             auto wc1 = iWhereClause1;
0682             if (wc1.isEmpty()) {
0683                 wc1 = SKGServices::getPeriodWhereClause(getPeriod());
0684             }
0685             auto wc2 = iWhereClause2;
0686             if (wc2.isEmpty()) {
0687                 wc2 = SKGServices::getPeriodWhereClause(getPreviousPeriod());
0688             }
0689 
0690             auto sqlFilter = getSqlFilter();
0691             if (sqlFilter.isEmpty()) {
0692                 sqlFilter = QStringLiteral("1=1");
0693             }
0694             SKGError err = doc->executeSelectSqliteOrder(
0695                                "SELECT TOTAL(" % amount % "), '1'  from " % tableDb % " WHERE " % wc1 % " AND " % sqlFilter
0696                                % (iGrouped ? "" : " AND i_group_id=0")
0697                                % (iTransfer ? "" : " AND t_TRANSFER='N'")
0698                                % (iTracker ? "" : (iOnSubOperation ? " AND t_REALREFUND=''" : " AND t_REFUND=''"))
0699                                % " group by t_TYPEEXPENSE "
0700                                % " UNION ALL "
0701                                % "SELECT TOTAL(" % amount % "), '2'  from " % tableDb % " WHERE " % wc2 % " AND " % sqlFilter
0702                                % (iGrouped ? "" : " AND i_group_id=0")
0703                                % (iTransfer ? "" : " AND t_TRANSFER='N'")
0704                                % (iTracker ? "" : (iOnSubOperation ? " AND t_REALREFUND=''" : " AND t_REFUND=''"))
0705                                % " group by t_TYPEEXPENSE ",
0706                                listTmp);
0707             IFOK(err) {
0708                 double income_previous_period = 0;
0709                 double expense_previous_period = 0;
0710                 double income_period = 0;
0711                 double expense_period = 0;
0712 
0713                 int nbval = listTmp.count();
0714                 for (int i = 1; i < nbval; ++i) {  // Ignore header
0715                     QString m = listTmp.at(i).at(1);
0716                     double v = SKGServices::stringToDouble(listTmp.at(i).at(0));
0717                     if (v > 0 && m == QStringLiteral("1")) {
0718                         income_period = v;
0719                     } else if (v < 0 && m == QStringLiteral("1")) {
0720                         expense_period = v;
0721                     } else if (v > 0 && m == QStringLiteral("2")) {
0722                         income_previous_period = v;
0723                     } else if (v < 0 && m == QStringLiteral("2")) {
0724                         expense_previous_period = v;
0725                     }
0726                 }
0727 
0728                 double saving_previous_period = income_previous_period + expense_previous_period;
0729                 double saving_period = income_period + expense_period;
0730 
0731                 table.push_back(QVariantList() << QStringLiteral("sum") << QLatin1String("") << getPreviousPeriod() << getPeriod() << QStringLiteral("max"));
0732                 table.push_back(QVariantList() << false << doc->getDisplay(QStringLiteral("f_CURRENTAMOUNT_INCOME")) << qAbs(income_previous_period) << qAbs(income_period));
0733                 table.push_back(QVariantList() << false << doc->getDisplay(QStringLiteral("f_CURRENTAMOUNT_EXPENSE")) << qAbs(expense_previous_period) << qAbs(expense_period));
0734                 table.push_back(QVariantList() << true << i18nc("Noun",  "Savings possible") << saving_previous_period << saving_period);
0735                 table.push_back(QVariantList() << true << i18nc("Noun",  "Max") << qMax(qAbs(income_previous_period), qAbs(expense_previous_period)) << qMax(qAbs(income_period), qAbs(expense_period)));
0736             }
0737             m_cache[cacheId] = table;
0738         }
0739     }
0740     return table;
0741 }
0742 
0743 void SKGReportBank::addItemsInMapping(QVariantHash& iMapping)
0744 {
0745     SKGReport::addItemsInMapping(iMapping);
0746     iMapping.insert(QStringLiteral("about_forumpage"), QStringLiteral("https://forum.kde.org/viewforum.php?f=210"));
0747     iMapping.insert(QStringLiteral("about_newspage"), QStringLiteral("https://skrooge.org/news"));
0748     iMapping.insert(QStringLiteral("about_operationpage"), QStringLiteral("skg://Skrooge_operation_plugin/"));
0749     iMapping.insert(QStringLiteral("about_accountpage"), QStringLiteral("skg://Skrooge_bank_plugin/"));
0750     iMapping.insert(QStringLiteral("about_importurl"), QStringLiteral("skg://import_operation/"));
0751     iMapping.insert(QStringLiteral("about_maintext"), i18nc("The main text of skrooge",
0752                     "Skrooge allows you to keep a hold on your expenses, by tracking and budgeting them.<br/>"
0753                     "What should you do now ?<br/>"
0754                     "<ul>"
0755                     "<li>Create at least one <a href=\"%1\">account</a></li>"
0756                     "<li>Add some transactions, using <a href=\"%3\">import</a> or <a href=\"%2\">manual input</a></li>"
0757                     "<li>Categorize them</li>"
0758                     "</ul>"
0759                     "<p>You may come back to this page any time by closing all tabs.<br/>"
0760                     "For more information about using Skrooge, check the <a href=\"https://skrooge.org\">Skrooge website</a>.</p>"
0761                     "<p>We hope that you will enjoy Skrooge</p>"
0762                     "    The Skrooge Team",
0763                     iMapping[QStringLiteral("about_accountpage")].toString(), iMapping[QStringLiteral("about_operationpage")].toString(), iMapping[QStringLiteral("about_importurl")].toString()));
0764     iMapping.insert(QStringLiteral("logo"), QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("icons/breeze/apps/48/skrooge.svg"))).url());
0765     iMapping.insert(QStringLiteral("logo_black"), QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("icons/breeze/apps/48/skrooge-black.svg"))).url());
0766 
0767     iMapping.insert(QStringLiteral("title_main"), i18nc("A monthly report title", "Report for %1", getPeriod()));
0768     iMapping.insert(QStringLiteral("title_budget"), i18nc("A monthly report title", "Budget"));
0769     iMapping.insert(QStringLiteral("title_main_categories"), i18nc("A monthly report title", "5 main categories of expenditure"));
0770     iMapping.insert(QStringLiteral("title_variations"), i18nc("A monthly report title", "5 main variations"));
0771     iMapping.insert(QStringLiteral("title_account"), i18nc("A monthly report title", "Amounts in accounts"));
0772     iMapping.insert(QStringLiteral("title_unit"), i18nc("A monthly report title", "Amounts of units"));
0773     iMapping.insert(QStringLiteral("title_advice"), i18nc("A monthly report title", "Advice"));
0774     iMapping.insert(QStringLiteral("title_portfolio"), i18nc("A monthly report title", "Stock portfolio"));
0775     iMapping.insert(QStringLiteral("title_interests"), i18nc("A monthly report title", "Estimated interests"));
0776     iMapping.insert(QStringLiteral("title_alarms"), i18nc("A monthly report title", "Alarms"));
0777     iMapping.insert(QStringLiteral("title_highlighted"), i18nc("A monthly report title", "Highlighted transactions"));
0778     iMapping.insert(QStringLiteral("title_networth"), i18nc("A monthly report title", "Net Worth"));
0779     iMapping.insert(QStringLiteral("title_annual_spending"), i18nc("A monthly report title", "Annual Spending"));
0780     iMapping.insert(QStringLiteral("title_personal_finance_score"), i18nc("A monthly report title", "Personal Finance Score"));
0781 
0782     iMapping.insert(QStringLiteral("msg_no_variation"), i18nc("A monthly report message", "No variation found."));
0783     iMapping.insert(QStringLiteral("msg_no_scheduled"), i18nc("A monthly report message", R"(No scheduled transactions defined on the "<a href="%1">Scheduled transactions</a>" page.)", "skg://Skrooge_scheduled_plugin/"));
0784     iMapping.insert(QStringLiteral("msg_no_highlighted"), i18nc("A monthly report message", R"(No highlighted transactions defined on the "<a href="%1">Transactions</a>" page.)", "skg://Skrooge_operation_plugin/"));
0785     iMapping.insert(QStringLiteral("msg_no_budget"), i18nc("A monthly report message", R"(No budget defined on the "<a href="%1">Budget</a>" page.)", "skg://Skrooge_budget_plugin/"));
0786     iMapping.insert(QStringLiteral("msg_no_share"), i18nc("A monthly report message", R"(No share defined on the "<a href="%1">Unit</a>" page.)", "skg://Skrooge_unit_plugin/"));
0787     iMapping.insert(QStringLiteral("msg_amount_unit_date"), i18nc("A monthly report message", "All amounts are calculated using the unit rates of the last day of the corresponding period."));
0788 }