File indexing completed on 2024-05-26 05:10:40

0001 
0002 /***************************************************************************
0003  * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr
0004  * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  ***************************************************************************/
0007 /** @file
0008  * This file is Skrooge plugin for transaction management.
0009  *
0010  * @author Stephane MANKOWSKI / Guillaume DE BURE
0011  */
0012 #include "skgoperationplugin.h"
0013 
0014 #include <kaboutdata.h>
0015 #include <kactioncollection.h>
0016 #include <kpluginfactory.h>
0017 #include <kstandardaction.h>
0018 #include <ktoolbarpopupaction.h>
0019 
0020 #include <qdom.h>
0021 #include <qstandardpaths.h>
0022 #include <qthread.h>
0023 
0024 #include "skgaccountobject.h"
0025 #include "skgbudgetobject.h"
0026 #include "skgcategoryobject.h"
0027 #include "skgdocumentbank.h"
0028 #include "skghtmlboardwidget.h"
0029 #include "skgmainpanel.h"
0030 #include "skgoperation_settings.h"
0031 #include "skgoperationboardwidgetqml.h"
0032 #include "skgoperationobject.h"
0033 #include "skgoperationpluginwidget.h"
0034 #include "skgpayeeobject.h"
0035 #include "skgrecurrentoperationobject.h"
0036 #include "skgruleobject.h"
0037 #include "skgsuboperationobject.h"
0038 #include "skgtableview.h"
0039 #include "skgtraces.h"
0040 #include "skgtrackerobject.h"
0041 #include "skgtransactionmng.h"
0042 
0043 /**
0044  * This plugin factory.
0045  */
0046 K_PLUGIN_CLASS_WITH_JSON(SKGOperationPlugin, "metadata.json")
0047 
0048 SKGOperationPlugin::SKGOperationPlugin(QWidget* iWidget, QObject* iParent, const QVariantList& /*iArg*/) :
0049     SKGInterfacePlugin(iParent),
0050     m_applyTemplateMenu(nullptr), m_openOperationsWithMenu(nullptr), m_openSubOperationsWithMenu(nullptr), m_currentBankDocument(nullptr)
0051 {
0052     Q_UNUSED(iWidget)
0053     SKGTRACEINFUNC(10)
0054 }
0055 
0056 SKGOperationPlugin::~SKGOperationPlugin()
0057 {
0058     SKGTRACEINFUNC(10)
0059     m_currentBankDocument = nullptr;
0060     m_applyTemplateMenu = nullptr;
0061     m_openOperationsWithMenu = nullptr;
0062     m_openSubOperationsWithMenu = nullptr;
0063 }
0064 
0065 bool SKGOperationPlugin::setupActions(SKGDocument* iDocument)
0066 {
0067     SKGTRACEINFUNC(10)
0068 
0069     m_currentBankDocument = qobject_cast<SKGDocumentBank*>(iDocument);
0070     if (m_currentBankDocument == nullptr) {
0071         return false;
0072     }
0073 
0074     m_currentBankDocument->setComputeBalances(skgoperation_settings::computeBalances());
0075     m_currentBankDocument->addEndOfTransactionCheck(SKGOperationPlugin::checkReconciliation);
0076     m_currentBankDocument->addEndOfTransactionCheck(SKGOperationPlugin::checkImport);
0077 
0078     setComponentName(QStringLiteral("skrooge_operation"), title());
0079     setXMLFile(QStringLiteral("skrooge_operation.rc"));
0080 
0081     QStringList listOperation;
0082     listOperation << QStringLiteral("operation");
0083 
0084     // Menu
0085     // ------------
0086     auto actDuplicateAction = new QAction(SKGServices::fromTheme(QStringLiteral("window-duplicate")), i18nc("Verb, duplicate an object",  "Duplicate"), this);
0087     connect(actDuplicateAction, &QAction::triggered, this, &SKGOperationPlugin::onDuplicate);
0088     actionCollection()->setDefaultShortcut(actDuplicateAction, Qt::CTRL + Qt::Key_D);
0089     registerGlobalAction(QStringLiteral("edit_duplicate_operation"), actDuplicateAction, listOperation, 1, -1, 400);
0090 
0091     // ------------
0092     auto actCreateTemplateAction = new QAction(SKGServices::fromTheme(QStringLiteral("edit-guides")), i18nc("Verb", "Create template"), this);
0093     connect(actCreateTemplateAction, &QAction::triggered, this, &SKGOperationPlugin::onCreateTemplate);
0094     actionCollection()->setDefaultShortcut(actCreateTemplateAction, Qt::CTRL + Qt::SHIFT + Qt::Key_T);
0095     registerGlobalAction(QStringLiteral("edit_template_operation"), actCreateTemplateAction, listOperation, 1, -1, 401);
0096 
0097     // ------------
0098     auto actSwitchToMarkedAction = new QAction(SKGServices::fromTheme(QStringLiteral("dialog-ok")), i18nc("Verb, mark an object", "Mark"), this);
0099     connect(actSwitchToMarkedAction, &QAction::triggered, this, &SKGOperationPlugin::onSwitchToMarked);
0100     actionCollection()->setDefaultShortcut(actSwitchToMarkedAction, Qt::CTRL + Qt::Key_R);
0101     registerGlobalAction(QStringLiteral("edit_mark_selected_operation"), actSwitchToMarkedAction, listOperation, 1, -1, 310);
0102 
0103     // ------------
0104     auto actFastEdition = new QAction(SKGServices::fromTheme(QStringLiteral("games-solve")), i18nc("Verb", "Fast edit"), this);
0105     actFastEdition->setEnabled(false);
0106     actionCollection()->setDefaultShortcut(actFastEdition, Qt::Key_F10);
0107     registerGlobalAction(QStringLiteral("fast_edition"), actFastEdition, listOperation);
0108 
0109     // ------------
0110     QStringList overlayopen;
0111     overlayopen.push_back(QStringLiteral("quickopen"));
0112 
0113     QStringList overlayrun;
0114     overlayrun.push_back(QStringLiteral("system-run"));
0115     auto actOpen = new QAction(SKGServices::fromTheme(icon(), overlayopen), i18nc("Verb", "Open transactions…"), this);
0116     connect(actOpen, &QAction::triggered, this, &SKGOperationPlugin::onOpenOperations);
0117     registerGlobalAction(QStringLiteral("open"), actOpen, QStringList() << QStringLiteral("account") << QStringLiteral("unit") << QStringLiteral("category") << QStringLiteral("refund") << QStringLiteral("payee") << QStringLiteral("budget") << QStringLiteral("recurrentoperation") << QStringLiteral("operation") << QStringLiteral("suboperation") << QStringLiteral("rule"),
0118                          1, -1, 110);
0119 
0120     auto actOpen2 = new KToolBarPopupAction(SKGServices::fromTheme(icon(), overlayopen), i18nc("Verb", "Open transactions with …"), this);
0121     m_openOperationsWithMenu = actOpen2->menu();
0122     connect(m_openOperationsWithMenu, &QMenu::aboutToShow, this, &SKGOperationPlugin::onShowOpenWithMenu);
0123     actOpen2->setStickyMenu(false);
0124     actOpen2->setDelayed(false);
0125     registerGlobalAction(QStringLiteral("open_operations_with"), actOpen2, QStringList() << QStringLiteral("operation") << QStringLiteral("suboperation"), 1, 1, 111);
0126 
0127     auto actOpen3 = new KToolBarPopupAction(SKGServices::fromTheme(icon(), overlayopen), i18nc("Verb", "Open sub transactions with …"), this);
0128     m_openSubOperationsWithMenu = actOpen3->menu();
0129     connect(m_openSubOperationsWithMenu, &QMenu::aboutToShow, this, &SKGOperationPlugin::onShowOpenWithMenu);
0130     actOpen3->setStickyMenu(false);
0131     actOpen3->setDelayed(false);
0132     registerGlobalAction(QStringLiteral("open_suboperations_with"), actOpen3, QStringList() << QStringLiteral("operation") << QStringLiteral("suboperation"), 1, 1, 112);
0133 
0134     auto actOpenHighLights = new QAction(SKGServices::fromTheme(QStringLiteral("bookmarks"), overlayopen), i18nc("Verb", "Open highlights…"), this);
0135     actOpenHighLights->setData(QString("skg://skrooge_operation_plugin/?title_icon=bookmarks&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Highlighted transactions")) %
0136                                        "&operationWhereClause=t_bookmarked='Y'"));
0137     connect(actOpenHighLights, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0138         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0139     });
0140     actionCollection()->setDefaultShortcut(actOpenHighLights, Qt::CTRL + Qt::META + Qt::Key_H);
0141     registerGlobalAction(QStringLiteral("view_open_highlight"), actOpenHighLights);
0142 
0143     // ------------
0144     auto actOpenLastModified = new QAction(SKGServices::fromTheme(QStringLiteral("view-refresh"), overlayopen), i18nc("Verb", "Open last modified…"), this);
0145     actOpenLastModified->setData(QString("skg://skrooge_operation_plugin/?title_icon=view-refresh&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions modified or created during last action")) %
0146                                          "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("id in (SELECT i_object_id FROM doctransactionitem di, doctransaction dt WHERE dt.t_mode='U' AND +di.rd_doctransaction_id=dt.id AND di.t_object_table='operation'AND NOT EXISTS(select 1 from doctransaction B where B.i_parent=dt.id))"))));
0147     connect(actOpenLastModified, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0148         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0149     });
0150     actionCollection()->setDefaultShortcut(actOpenLastModified, Qt::META + Qt::Key_L);
0151     registerGlobalAction(QStringLiteral("view_open_last_modified"), actOpenLastModified);
0152 
0153     // ------------
0154     auto actOpenModifiedByTransaction = new QAction(SKGServices::fromTheme(QStringLiteral("view-refresh"), overlayopen), i18nc("Verb", "Open transactions modified by this transaction…"), this);
0155     connect(actOpenModifiedByTransaction, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0156         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0157         if (!selection.isEmpty()) {
0158             SKGObjectBase obj = selection[0];
0159             QString name = obj.getAttribute(QStringLiteral("t_name"));
0160             QString url = QString("skg://skrooge_operation_plugin/?title_icon=view-refresh&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions modified or created during the action '%1'", name)) % "&operationWhereClause=" % SKGServices::encodeForUrl("id in (SELECT i_object_id FROM doctransactionitem WHERE rd_doctransaction_id=" % SKGServices::intToString(obj.getID()) % " AND t_object_table='operation')"));
0161             SKGMainPanel::getMainPanel()->SKGMainPanel::openPage(url);
0162         }
0163     });
0164     registerGlobalAction(QStringLiteral("view_open_modified_by_transaction"), actOpenModifiedByTransaction, QStringList() << QStringLiteral("doctransaction"), 1, 1, 100);
0165 
0166     // ------------
0167     auto actOpenSuboperations = new QAction(SKGServices::fromTheme(QStringLiteral("split"), overlayopen), i18nc("Verb", "Open sub transactions…"), this);
0168     actOpenSuboperations->setData(QString("skg://skrooge_operation_plugin/SKGOPERATION_CONSOLIDATED_DEFAULT_PARAMETERS/?title_icon=split&operationTable=v_suboperation_consolidated&operationWhereClause=&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Sub transactions")) % "#" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Sub transactions"))));
0169     connect(actOpenSuboperations, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0170         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0171     });
0172     actionCollection()->setDefaultShortcut(actOpenSuboperations, Qt::META + Qt::Key_S);
0173     registerGlobalAction(QStringLiteral("view_open_suboperations"), actOpenSuboperations);
0174 
0175     // ------------
0176     auto actOpenDuplicate = new QAction(SKGServices::fromTheme(QStringLiteral("window-duplicate"), overlayopen), i18nc("Verb", "Open potential duplicates…"), this);
0177     actOpenDuplicate->setData(QString("skg://skrooge_operation_plugin/?title_icon=window-duplicate&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions potentially duplicated")) %
0178                                       "&operationWhereClause=" % SKGServices::encodeForUrl("id in (SELECT o1.id FROM v_operation o1 WHERE EXISTS (SELECT 1 FROM v_operation o2 WHERE o1.id<>o2.id AND o1.t_template='N' AND o2.t_template='N' AND o1.d_date=o2.d_date  AND ABS(o1.f_CURRENTAMOUNT-o2.f_CURRENTAMOUNT)<" % SKGServices::doubleToString(EPSILON) % " AND o1.rd_account_id=o2.rd_account_id AND o1.rc_unit_id=o2.rc_unit_id AND (o1.t_status<>'Y' OR o2.t_status<>'Y')))")));
0179     connect(actOpenDuplicate, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0180         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0181     });
0182     actionCollection()->setDefaultShortcut(actOpenDuplicate, Qt::META + Qt::Key_D);
0183     registerGlobalAction(QStringLiteral("view_open_duplicates"), actOpenDuplicate);
0184 
0185     // ------------
0186     auto actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("exchange-positions"), overlayopen), i18nc("Verb", "Open transfers without payee…"), this);
0187     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=user-group-properties&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transfers without payee")) %
0188                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("t_TRANSFER='Y' AND r_payee_id=0"))));
0189     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0190         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0191     });
0192     registerGlobalAction(QStringLiteral("view_open_transfers_without_payee"), actTmp);
0193 
0194     // ------------
0195     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("user-group-properties"), overlayopen), i18nc("Verb", "Open transactions without payee…"), this);
0196     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=user-group-properties&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions without payee")) %
0197                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("t_TRANSFER='N' AND r_payee_id=0"))));
0198     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0199         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0200     });
0201     registerGlobalAction(QStringLiteral("view_open_operation_without_payee"), actTmp);
0202 
0203     // ------------
0204     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("exchange-positions"), overlayopen), i18nc("Verb", "Open transfers without category…"), this);
0205     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=view-categories&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transfers without category")) %
0206                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("t_TRANSFER='Y' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)"))));
0207     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0208         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0209     });
0210     registerGlobalAction(QStringLiteral("view_open_transfers_without_category"), actTmp);
0211 
0212     // ------------
0213     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("view-categories"), overlayopen), i18nc("Verb", "Open transactions without category…"), this);
0214     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=view-categories&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions without category")) %
0215                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("t_TRANSFER='N' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)"))));
0216     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0217         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0218     });
0219     registerGlobalAction(QStringLiteral("view_open_operation_without_category"), actTmp);
0220 
0221     // ------------
0222     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("skrooge_credit_card"), overlayopen), i18nc("Verb", "Open transactions without mode…"), this);
0223     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=skrooge_credit_card&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions without mode")) %
0224                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("t_template='N' AND t_mode='' AND d_date<>'0000-00-00'"))));
0225     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0226         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0227     });
0228     registerGlobalAction(QStringLiteral("view_open_operation_without_mode"), actTmp);
0229 
0230     // ------------
0231     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("draw-freehand"), overlayopen), i18nc("Verb", "Open transactions with comments not aligned…"), this);
0232     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=draw-freehand&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions with comments not aligned")) %
0233                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("id IN (SELECT op.id FROM operation op, suboperation so WHERE so.rd_operation_id=op.id AND so.t_comment<>op.t_comment AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1)"))));
0234     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0235         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0236     });
0237     registerGlobalAction(QStringLiteral("view_open_operation_with_comment_not_aligned"), actTmp);
0238 
0239     // ------------
0240     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("view-pim-calendar"), overlayopen), i18nc("Verb", "Open transactions with dates not aligned…"), this);
0241     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=draw-freehand&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions with dates not aligned")) %
0242                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("id IN (SELECT op.id FROM operation op, suboperation so WHERE so.rd_operation_id=op.id AND (so.d_date<op.d_date OR (so.d_date<>op.d_date AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1)))"))));
0243     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0244         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0245     });
0246     registerGlobalAction(QStringLiteral("view_open_operation_with_date_not_aligned"), actTmp);
0247 
0248     // ------------
0249     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("exchange-positions"), overlayopen), i18nc("Verb", "Open transactions without payees category…"), this);
0250     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=view-categories&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Sub-transactions without payees category")) %
0251                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("id IN (SELECT v_suboperation_consolidated.i_OPID FROM v_suboperation_consolidated INNER JOIN payee ON v_suboperation_consolidated.r_payee_id=payee.id WHERE payee.r_category_id!=0 AND v_suboperation_consolidated.r_category_id!=payee.r_category_id AND i_NBSUBOPERATIONS=1)"))));
0252     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0253         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0254     });
0255     registerGlobalAction(QStringLiteral("view_open_suboperations_without_payee_category"), actTmp);
0256 
0257     // ------------
0258     auto actAlignComment = new QAction(SKGServices::fromTheme(QStringLiteral("draw-freehand"), overlayrun), i18nc("Verb", "Align comment of subtransactions of all single transactions"), this);
0259     connect(actAlignComment, &QAction::triggered, this, &SKGOperationPlugin::onAlignComment);
0260     registerGlobalAction(QStringLiteral("align_comment"), actAlignComment);
0261 
0262     auto actAlignComment2 = new QAction(SKGServices::fromTheme(QStringLiteral("draw-freehand"), overlayrun), i18nc("Verb", "Align comment of transactions of all single transactions"), this);
0263     connect(actAlignComment2, &QAction::triggered, this, &SKGOperationPlugin::onAlignComment2);
0264     registerGlobalAction(QStringLiteral("align_comment2"), actAlignComment2);
0265 
0266     // ------------
0267     auto actAlignDate = new QAction(SKGServices::fromTheme(QStringLiteral("view-pim-calendar"), overlayrun), i18nc("Verb", "Align date of subtransactions of all transactions"), this);
0268     connect(actAlignDate, &QAction::triggered, this, &SKGOperationPlugin::onAlignDate);
0269     registerGlobalAction(QStringLiteral("align_date"), actAlignDate);
0270 
0271     auto actAlignCategory = new QAction(SKGServices::fromTheme(QStringLiteral("view-pim-calendar"), overlayrun), i18nc("Verb", "Align the category of all single transactions with the category of their payee"), this);
0272     connect(actAlignCategory, &QAction::triggered, this, &SKGOperationPlugin::onAlignWithCategoryOfPayee);
0273     registerGlobalAction(QStringLiteral("align_category"), actAlignCategory);
0274 
0275 
0276     // ------------
0277     actTmp = new QAction(SKGServices::fromTheme(QStringLiteral("exchange-positions"), overlayopen), i18nc("Verb", "Open transactions in groups with only one transaction…"), this);
0278     actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=exchange-positions&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions in groups with only one transaction")) %
0279                             "&operationWhereClause=" % SKGServices::encodeForUrl(QStringLiteral("v_operation_display.i_group_id<>0 AND (SELECT COUNT(1) FROM operation o WHERE o.i_group_id=v_operation_display.i_group_id)<2"))));
0280     connect(actTmp, &QAction::triggered, SKGMainPanel::getMainPanel(), [ = ]() {
0281         SKGMainPanel::getMainPanel()->SKGMainPanel::openPage();
0282     });
0283     registerGlobalAction(QStringLiteral("view_open_operation_in_group_of_one"), actTmp);
0284 
0285     // ------------
0286     auto actCleanRemoveGroupWithOneOperation = new QAction(SKGServices::fromTheme(QStringLiteral("exchange-positions"), overlayrun), i18nc("Verb", "Remove groups with only one transaction of all transactions"), this);
0287     connect(actCleanRemoveGroupWithOneOperation, &QAction::triggered, this, &SKGOperationPlugin::onRemoveGroupWithOneOperation);
0288     registerGlobalAction(QStringLiteral("clean_remove_group_with_one_operation"), actCleanRemoveGroupWithOneOperation);
0289 
0290     // ------------
0291     auto actGroupOperation = new QAction(SKGServices::fromTheme(QStringLiteral("exchange-positions")), i18nc("Verb", "Group transactions"), this);
0292     connect(actGroupOperation, &QAction::triggered, this, &SKGOperationPlugin::onGroupOperation);
0293     actionCollection()->setDefaultShortcut(actGroupOperation, Qt::CTRL + Qt::Key_G);
0294     registerGlobalAction(QStringLiteral("edit_group_operation"), actGroupOperation, listOperation, 2, -1, 311);
0295 
0296     // ------------
0297     QStringList overlay;
0298     overlay.push_back(QStringLiteral("edit-delete"));
0299     auto actUngroupOperation = new QAction(SKGServices::fromTheme(QStringLiteral("exchange-positions"), overlay), i18nc("Verb", "Ungroup transactions"), this);
0300     connect(actUngroupOperation, &QAction::triggered, this, &SKGOperationPlugin::onUngroupOperation);
0301     actionCollection()->setDefaultShortcut(actUngroupOperation, Qt::CTRL + Qt::SHIFT + Qt::Key_G);
0302     registerGlobalAction(QStringLiteral("edit_ungroup_operation"), actUngroupOperation, listOperation, 1, -1, 312);
0303 
0304     // ------------
0305     auto actMergeOperationAction = new QAction(SKGServices::fromTheme(QStringLiteral("split")), i18nc("Verb, action to merge", "Merge sub transactions"), this);
0306     connect(actMergeOperationAction, &QAction::triggered, this, &SKGOperationPlugin::onMergeSubOperations);
0307     actionCollection()->setDefaultShortcut(actMergeOperationAction, Qt::CTRL + Qt::SHIFT + Qt::Key_M);
0308     registerGlobalAction(QStringLiteral("merge_sub_operations"), actMergeOperationAction, listOperation, 1, -1, 320);
0309 
0310     auto actApplyTemplateAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("edit-guides")), i18nc("Verb, action to apply a template", "Apply template"), this);
0311     m_applyTemplateMenu = actApplyTemplateAction->menu();
0312     connect(m_applyTemplateMenu, &QMenu::aboutToShow, this, &SKGOperationPlugin::onShowApplyTemplateMenu);
0313     actApplyTemplateAction->setStickyMenu(false);
0314     actApplyTemplateAction->setData(1);
0315     registerGlobalAction(QStringLiteral("edit_apply_template"), actApplyTemplateAction, listOperation, 1, -1, 402);
0316 
0317     return true;
0318 }
0319 
0320 void SKGOperationPlugin::onShowApplyTemplateMenu()
0321 {
0322     if ((m_applyTemplateMenu != nullptr) && (m_currentBankDocument != nullptr)) {
0323         // Clean Menu
0324         QMenu* m = m_applyTemplateMenu;
0325         m->clear();
0326 
0327         // Search templates
0328         SKGStringListList listTmp;
0329         m_currentBankDocument->executeSelectSqliteOrder(
0330             QStringLiteral("SELECT t_displayname, id, t_bookmarked FROM v_operation_displayname WHERE t_template='Y' ORDER BY t_bookmarked DESC, t_PAYEE ASC"),
0331             listTmp);
0332 
0333         // Build menus
0334         int count = 0;
0335         bool fav = true;
0336         int nb = listTmp.count();
0337         for (int i = 1; i < nb; ++i) {
0338             // Add more sub menu
0339             if (count == 8) {
0340                 m = m->addMenu(i18nc("More items in a menu", "More"));
0341                 count = 0;
0342             }
0343             count++;
0344 
0345             // Add separator for bookmarked templates
0346             if (fav && listTmp.at(i).at(2) == QStringLiteral("N") && i > 1) {
0347                 m->addSeparator();
0348             }
0349             fav = (listTmp.at(i).at(2) == QStringLiteral("Y"));
0350 
0351             // Add actions
0352             QAction* act = m->addAction(SKGServices::fromTheme(QStringLiteral("edit-guides")), listTmp.at(i).at(0));
0353             if (act != nullptr) {
0354                 act->setData(listTmp.at(i).at(1));
0355                 connect(act, &QAction::triggered, this, &SKGOperationPlugin::onApplyTemplate);
0356             }
0357         }
0358     }
0359 }
0360 
0361 void SKGOperationPlugin::refresh()
0362 {
0363     SKGTRACEINFUNC(10)
0364     if ((m_currentBankDocument != nullptr) && (SKGMainPanel::getMainPanel() != nullptr)) {
0365         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0366         int nb = selection.count();
0367         bool onOperation = (nb > 0 && selection.at(0).getRealTable() == QStringLiteral("operation"));
0368 
0369         QAction* act = SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("align_date"));
0370         act->setText(onOperation ? i18nc("Verb", "Align date of subtransactions of selected single transactions") : i18nc("Verb", "Align date of subtransactions of all single transactions"));
0371         act->setData(onOperation);
0372 
0373         act = SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("align_comment2"));
0374         act->setText(onOperation ? i18nc("Verb", "Align comment of transactions of selected single transactions") : i18nc("Verb", "Align comment of transactions of all single transactions"));
0375         act->setData(onOperation);
0376 
0377         act = SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("align_category"));
0378         act->setText(onOperation ? i18nc("Verb", "Align the category of all selected single transactions with the category of their payee") : i18nc("Verb", "Align the category of all single transactions with the category of their payee"));
0379         act->setData(onOperation);
0380 
0381         act = SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("align_comment"));
0382         act->setText(onOperation ? i18nc("Verb", "Align comment of subtransactions of selected single transactions") : i18nc("Verb", "Align comment of subtransactions of all single transactions"));
0383         act->setData(onOperation);
0384 
0385         act = SKGMainPanel::getMainPanel()->getGlobalAction(QStringLiteral("clean_remove_group_with_one_operation"));
0386         act->setText(onOperation ? i18nc("Verb", "Remove groups with only one transaction of selected transactions") : i18nc("Verb", "Remove groups with only one transaction of all transactions"));
0387         act->setData(onOperation);
0388     }
0389 }
0390 
0391 SKGError SKGOperationPlugin::checkReconciliation(SKGDocument* iDocument)
0392 {
0393     SKGError err;
0394     SKGTRACEINFUNCRC(5, err)
0395     if ((iDocument != nullptr) && (SKGMainPanel::getMainPanel() != nullptr) && skgoperation_settings::broken_reconciliation() > QStringLiteral("0")) {
0396         // Check the reconciliation for all opened account
0397         SKGObjectBase::SKGListSKGObjectBase accounts;
0398         iDocument->getObjects(QStringLiteral("v_account"), QStringLiteral("t_close='N' AND f_reconciliationbalance!='' AND (SELECT COUNT(1) FROM (SELECT DISTINCT(operation.rc_unit_id) FROM operation WHERE operation.rd_account_id=v_account.id GROUP BY operation.rc_unit_id))=1"), accounts);
0399         for (const auto& account : qAsConst(accounts)) {
0400             SKGAccountObject a(account);
0401             auto soluces = a.getPossibleReconciliations(SKGServices::stringToDouble(account.getAttribute(QStringLiteral("f_reconciliationbalance"))), false);
0402             if (soluces.isEmpty()) {
0403                 if (skgoperation_settings::broken_reconciliation() == QStringLiteral("1")) {
0404                     iDocument->sendMessage(i18nc("Warning message", "The previous reconciliation of '%1' has been broken by this action or a previous one.", a.getDisplayName()), SKGDocument::Warning, QStringLiteral("skg://edit_undo"));
0405                 } else {
0406                     auto msg = i18nc("Warning message", "This action would break the previous reconciliation of '%1', so it is cancelled.", a.getDisplayName());
0407                     iDocument->sendMessage(msg, SKGDocument::Error);
0408                     return err = SKGError(ERR_ABORT, msg);
0409                 }
0410             }
0411         }
0412     }
0413     return err;
0414 }
0415 
0416 SKGError SKGOperationPlugin::checkImport(SKGDocument* iDocument)
0417 {
0418     SKGError err;
0419     SKGTRACEINFUNCRC(5, err)
0420     if ((iDocument != nullptr) && (SKGMainPanel::getMainPanel() != nullptr) && skgoperation_settings::broken_import() > QStringLiteral("0")) {
0421         // Check the reconciliation for all opened account
0422         SKGObjectBase::SKGListSKGObjectBase accounts;
0423         iDocument->getObjects(QStringLiteral("v_account"), QStringLiteral("t_close='N' AND f_importbalance!=''"), accounts);
0424         for (const auto& account : qAsConst(accounts)) {
0425             SKGAccountObject a(account);
0426             auto soluces = a.getPossibleReconciliations(SKGServices::stringToDouble(account.getAttribute(QStringLiteral("f_importbalance"))), false);
0427             if (soluces.isEmpty()) {
0428                 if (skgoperation_settings::broken_import() == QStringLiteral("1")) {
0429                     iDocument->sendMessage(i18nc("Warning message", "The previous import in '%1' has been broken by this action or a previous one.", a.getDisplayName()), SKGDocument::Warning, QStringLiteral("skg://edit_undo"));
0430                 } else {
0431                     auto msg = i18nc("Warning message", "This action would break the previous import in '%1', so it is cancelled.", a.getDisplayName());
0432                     iDocument->sendMessage(msg, SKGDocument::Error);
0433                     return err = SKGError(ERR_ABORT, msg);
0434                 }
0435             }
0436         }
0437     }
0438     return err;
0439 }
0440 
0441 int SKGOperationPlugin::getNbDashboardWidgets()
0442 {
0443     return 2;
0444 }
0445 
0446 QString SKGOperationPlugin::getDashboardWidgetTitle(int iIndex)
0447 {
0448     if (iIndex == 0) {
0449         return i18nc("Noun, the title of a section", "Income && Expenditure");
0450     }
0451     return i18nc("Noun, the title of a section", "Highlighted transactions");
0452 }
0453 
0454 SKGBoardWidget* SKGOperationPlugin::getDashboardWidget(int iIndex)
0455 {
0456     if (iIndex == 0) {
0457         return new SKGOperationBoardWidgetQml(SKGMainPanel::getMainPanel(), m_currentBankDocument);
0458     }
0459     return new SKGHtmlBoardWidget(SKGMainPanel::getMainPanel(), m_currentBankDocument,
0460                                   getDashboardWidgetTitle(iIndex),
0461                                   QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("skrooge/html/default/highlighted_operations.html")),
0462                                   QStringList() << QStringLiteral("operation"));
0463 }
0464 
0465 SKGTabPage* SKGOperationPlugin::getWidget()
0466 {
0467     SKGTRACEINFUNC(10)
0468     return new SKGOperationPluginWidget(SKGMainPanel::getMainPanel(), m_currentBankDocument);
0469 }
0470 
0471 QWidget* SKGOperationPlugin::getPreferenceWidget()
0472 {
0473     SKGTRACEINFUNC(10)
0474     auto w = new QWidget();
0475     ui.setupUi(w);
0476 
0477     // Set labels
0478     ui.kPayeeFakeLbl->setText(i18n("%1:", m_currentBankDocument->getDisplay(QStringLiteral("t_payee"))));
0479     ui.kCategoryFakeLbl->setText(i18n("%1:", m_currentBankDocument->getDisplay(QStringLiteral("t_CATEGORY"))));
0480     ui.kCommentFakeLbl->setText(i18n("%1:", m_currentBankDocument->getDisplay(QStringLiteral("t_comment"))));
0481 
0482     ui.kCategoryCommissionLbl->setText(ui.kCategoryFakeLbl->text());
0483     ui.kCommentCommissionLbl->setText(ui.kCommentFakeLbl->text());
0484 
0485     ui.kCategoryTaxLbl->setText(ui.kCategoryFakeLbl->text());
0486     ui.kCommentTaxLbl->setText(ui.kCommentFakeLbl->text());
0487 
0488     // Fill combo boxes and auto complession
0489     SKGMainPanel::fillWithDistinctValue(QList<QWidget*>() << ui.kcfg_categoryFakeOperation << ui.kcfg_categoryCommissionOperation << ui.kcfg_categoryTaxOperation, m_currentBankDocument, QStringLiteral("category"), QStringLiteral("t_fullname"), QLatin1String(""));
0490     SKGMainPanel::fillWithDistinctValue(QList<QWidget*>() << ui.kcfg_payeeFakeOperation, m_currentBankDocument, QStringLiteral("payee"), QStringLiteral("t_name"), QLatin1String(""));
0491     SKGMainPanel::fillWithDistinctValue(QList<QWidget*>() << ui.kcfg_commentFakeOperation << ui.kcfg_commentCommissionOperation << ui.kcfg_commentTaxOperation, m_currentBankDocument, QStringLiteral("v_operation_all_comment"), QStringLiteral("t_comment"), QLatin1String(""), true);
0492 
0493     return w;
0494 }
0495 
0496 KConfigSkeleton* SKGOperationPlugin::getPreferenceSkeleton()
0497 {
0498     return skgoperation_settings::self();
0499 }
0500 
0501 SKGError SKGOperationPlugin::savePreferences() const
0502 {
0503     m_currentBankDocument->setComputeBalances(skgoperation_settings::computeBalances());
0504     return SKGInterfacePlugin::savePreferences();
0505 }
0506 
0507 QString SKGOperationPlugin::title() const
0508 {
0509     return i18nc("Noun", "Transactions");
0510 }
0511 
0512 QString SKGOperationPlugin::icon() const
0513 {
0514     return QStringLiteral("view-bank-account");
0515 }
0516 
0517 QString SKGOperationPlugin::toolTip() const
0518 {
0519     return i18nc("Noun", "Transaction management");
0520 }
0521 
0522 int SKGOperationPlugin::getOrder() const
0523 {
0524     return 15;
0525 }
0526 
0527 QStringList SKGOperationPlugin::tips() const
0528 {
0529     QStringList output;
0530     output.push_back(i18nc("Description of a tips", "<p>… you can press +, -, CTRL + or CTRL - to quickly change dates.</p>"));
0531     output.push_back(i18nc("Description of a tips", "<p>… you can update many <a href=\"skg://skrooge_operation_plugin\">transactions</a> in one shot.</p>"));
0532     output.push_back(i18nc("Description of a tips", "<p>… you can double click on an <a href=\"skg://skrooge_operation_plugin\">operation</a> to show or edit sub transactions.</p>"));
0533     output.push_back(i18nc("Description of a tips", "<p>… you can duplicate an <a href=\"skg://skrooge_operation_plugin\">operation</a> including complex transactions (split, grouped, …).</p>"));
0534     output.push_back(i18nc("Description of a tips", "<p>… you can create template of <a href=\"skg://skrooge_operation_plugin\">transactions</a>.</p>"));
0535     output.push_back(i18nc("Description of a tips", "<p>… you can group and ungroup <a href=\"skg://skrooge_operation_plugin\">transactions</a>.</p>"));
0536     output.push_back(i18nc("Description of a tips", "<p>… you have to indicate the sign of an <a href=\"skg://skrooge_operation_plugin\">operation</a> only if you want to force it, else it will be determined automatically with the <a href=\"skg://skrooge_category_plugin\">category</a>.</p>"));
0537     return output;
0538 }
0539 
0540 bool SKGOperationPlugin::isInPagesChooser() const
0541 {
0542     return true;
0543 }
0544 
0545 void SKGOperationPlugin::onApplyTemplate()
0546 {
0547     SKGError err;
0548     SKGTRACEINFUNCRC(10, err)
0549     auto* act = qobject_cast<QAction*>(sender());
0550     if (act != nullptr) {
0551         // Get template
0552         SKGOperationObject temp(m_currentBankDocument, SKGServices::stringToInt(act->data().toString()));
0553 
0554         // Get Selection
0555         if ((SKGMainPanel::getMainPanel() != nullptr) && (m_currentBankDocument != nullptr)) {
0556             QStringList listUUID;
0557 
0558             SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0559             int nb = selection.count();
0560             {
0561                 SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Apply template"), err, nb)
0562                 for (int i = 0; !err && i < nb; ++i) {
0563                     SKGOperationObject operationObj(selection.at(i));
0564 
0565                     SKGOperationObject op;
0566                     IFOKDO(err, temp.duplicate(op))
0567                     IFOKDO(err, op.mergeAttribute(operationObj, SKGOperationObject::PROPORTIONAL, false))
0568                     listUUID.push_back(op.getUniqueID());
0569 
0570                     IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
0571                 }
0572             }
0573 
0574             // status bar
0575             IFOK(err) {
0576                 err = SKGError(0, i18nc("Successful message after an user action", "Template applied."));
0577                 auto* w = qobject_cast<SKGOperationPluginWidget*>(SKGMainPanel::getMainPanel()->currentPage());
0578                 if (w != nullptr) {
0579                     w->getTableView()->selectObjects(listUUID, true);
0580                 }
0581             } else {
0582                 err.addError(ERR_FAIL, i18nc("Error message",  "Apply of template failed"));
0583             }
0584         }
0585         // Display error
0586         SKGMainPanel::displayErrorMessage(err);
0587     }
0588 }
0589 
0590 void SKGOperationPlugin::onGroupOperation()
0591 {
0592     SKGError err;
0593     SKGTRACEINFUNCRC(10, err)
0594     // Get Selection
0595     if ((SKGMainPanel::getMainPanel() != nullptr) && (m_currentBankDocument != nullptr)) {
0596         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0597         int nb = selection.count();
0598         if (nb >= 2) {
0599             SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Group transactions"), err, nb)
0600             SKGOperationObject main(selection.at(0));
0601             IFOKDO(err, m_currentBankDocument->stepForward(1))
0602             for (int i = 1; !err && i < nb; ++i) {
0603                 SKGOperationObject operationObj(selection.at(i));
0604                 IFOKDO(err, operationObj.setGroupOperation(main))
0605                 IFOKDO(err, operationObj.save())
0606                 IFOKDO(err, main.load())
0607 
0608                 // Send message
0609                 IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The transaction '%1' has been grouped with '%2'", operationObj.getDisplayName(), main.getDisplayName()), SKGDocument::Hidden))
0610 
0611                 IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
0612             }
0613         }
0614 
0615         // status bar
0616         IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Transactions grouped.")))
0617         else {
0618             err.addError(ERR_FAIL, i18nc("Error message",  "Group creation failed"));
0619         }
0620 
0621         // Display error
0622         SKGMainPanel::displayErrorMessage(err);
0623     }
0624 }
0625 
0626 void SKGOperationPlugin::onUngroupOperation()
0627 {
0628     SKGError err;
0629     SKGTRACEINFUNCRC(10, err)
0630     // Get Selection
0631     if ((SKGMainPanel::getMainPanel() != nullptr) && (m_currentBankDocument != nullptr)) {
0632         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0633         int nb = selection.count();
0634         {
0635             SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Ungroup transaction"), err, nb)
0636             for (int i = 0; !err && i < nb; ++i) {
0637                 SKGOperationObject operationObj(selection.at(i));
0638                 IFOKDO(err, operationObj.setGroupOperation(operationObj))
0639                 IFOKDO(err, operationObj.save())
0640 
0641                 // Send message
0642                 IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The transaction '%1' has been ungrouped", operationObj.getDisplayName()), SKGDocument::Hidden))
0643 
0644                 IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
0645             }
0646         }
0647 
0648         // status bar
0649         IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Transaction ungrouped.")))
0650         else {
0651             err.addError(ERR_FAIL, i18nc("Error message",  "Group deletion failed"));
0652         }
0653 
0654         // Display error
0655         SKGMainPanel::displayErrorMessage(err);
0656     }
0657 }
0658 
0659 void SKGOperationPlugin::onSwitchToMarked()
0660 {
0661     SKGError err;
0662     SKGTRACEINFUNCRC(10, err)
0663     // Get Selection
0664     if ((SKGMainPanel::getMainPanel() != nullptr) && (m_currentBankDocument != nullptr)) {
0665         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0666         int nb = selection.count();
0667         {
0668             SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Switch to marked"), err, nb)
0669             for (int i = 0; !err && i < nb; ++i) {
0670                 SKGOperationObject operationObj(selection.at(i));
0671                 IFOKDO(err, operationObj.setStatus(operationObj.getStatus() != SKGOperationObject::MARKED ? SKGOperationObject::MARKED : SKGOperationObject::NONE))
0672                 IFOKDO(err, operationObj.save())
0673 
0674                 // Send message
0675                 IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The status of the transaction '%1' has been changed", operationObj.getDisplayName()), SKGDocument::Hidden))
0676 
0677                 IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
0678             }
0679         }
0680 
0681         // status bar
0682         IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Transaction marked.")))
0683         else {
0684             err.addError(ERR_FAIL, i18nc("Error message",  "Switch failed"));
0685         }
0686 
0687         // Display error
0688         SKGMainPanel::displayErrorMessage(err);
0689     }
0690 }
0691 
0692 void SKGOperationPlugin::onDuplicate()
0693 {
0694     SKGError err;
0695     SKGTRACEINFUNCRC(10, err)
0696     // Get Selection
0697     if ((SKGMainPanel::getMainPanel() != nullptr) && (m_currentBankDocument != nullptr)) {
0698         QStringList listUUID;
0699         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0700         int nb = selection.count();
0701         {
0702             SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Duplicate transaction"), err, nb)
0703             for (int i = 0; !err && i < nb; ++i) {
0704                 SKGOperationObject operationObj(selection.at(i));
0705                 SKGOperationObject dup;
0706                 IFOKDO(err, operationObj.duplicate(dup))
0707                 IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
0708 
0709                 // Send message
0710                 IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The duplicate transaction '%1' has been added", dup.getDisplayName()), SKGDocument::Hidden))
0711 
0712 
0713                 listUUID.push_back(dup.getUniqueID());
0714             }
0715         }
0716 
0717         // status bar
0718         IFOK(err) {
0719             err = SKGError(0, i18nc("Successful message after an user action", "Transaction duplicated."));
0720             auto* w = qobject_cast<SKGOperationPluginWidget*>(SKGMainPanel::getMainPanel()->currentPage());
0721             if (w != nullptr) {
0722                 w->getTableView()->selectObjects(listUUID, true);
0723             }
0724         } else {
0725             err.addError(ERR_FAIL, i18nc("Error message",  "Duplicate transaction failed"));
0726         }
0727 
0728         // Display error
0729         SKGMainPanel::displayErrorMessage(err);
0730     }
0731 }
0732 
0733 void SKGOperationPlugin::onCreateTemplate()
0734 {
0735     SKGError err;
0736     SKGTRACEINFUNCRC(10, err)
0737     // Get Selection
0738     if ((SKGMainPanel::getMainPanel() != nullptr) && (m_currentBankDocument != nullptr)) {
0739         QStringList listUUID;
0740         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0741         int nb = selection.count();
0742         {
0743             SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Create template"), err, nb)
0744             for (int i = 0; !err && i < nb; ++i) {
0745                 SKGOperationObject operationObj(selection.at(i));
0746                 SKGOperationObject dup;
0747                 IFOKDO(err, operationObj.duplicate(dup, QDate::currentDate(), true))
0748                 IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
0749 
0750                 // Send message
0751                 IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The template '%1' has been added", dup.getDisplayName()), SKGDocument::Hidden))
0752 
0753                 listUUID.push_back(dup.getUniqueID());
0754             }
0755         }
0756 
0757         // status bar
0758         IFOK(err) {
0759             err = SKGError(0, i18nc("Successful message after an user action", "Template created."));
0760             auto* w = qobject_cast<SKGOperationPluginWidget*>(SKGMainPanel::getMainPanel()->currentPage());
0761             if (w != nullptr) {
0762                 w->setTemplateMode(true);
0763                 w->getTableView()->selectObjects(listUUID, true);
0764             }
0765         } else {
0766             err.addError(ERR_FAIL, i18nc("Error message",  "Creation template failed"));
0767         }
0768 
0769         // Display error
0770         SKGMainPanel::displayErrorMessage(err);
0771     }
0772 }
0773 
0774 void SKGOperationPlugin::onOpenOperations()
0775 {
0776     SKGTRACEINFUNC(10)
0777     if (SKGMainPanel::getMainPanel() != nullptr) {
0778         SKGObjectBase::SKGListSKGObjectBase selection;
0779         auto* act = qobject_cast< QAction* >(sender());
0780         if (act != nullptr) {
0781             QStringList data = SKGServices::splitCSVLine(act->data().toString(), '-');
0782             if (data.count() == 2) {
0783                 selection.push_back(SKGObjectBase(m_currentBankDocument, data[1], SKGServices::stringToInt(data[0])));
0784             }
0785         }
0786         if (selection.isEmpty()) {
0787             selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
0788         }
0789 
0790         int nb = selection.count();
0791         if (nb > 0) {
0792             QString wc;
0793             QString titleOpen;
0794             QString iconOpen;
0795             QString table = selection.at(0).getRealTable();
0796             QString view;
0797             QString account;
0798             if (table == QStringLiteral("account")) {
0799                 if (nb == 1) {
0800                     SKGAccountObject tmp(selection.at(0));
0801                     account = tmp.getName();
0802                 } else {
0803                     // Build whereclause and title
0804                     wc = QStringLiteral("rd_account_id in (");
0805                     titleOpen = i18nc("Noun, a list of items", "Transactions of account:");
0806 
0807                     for (int i = 0; i < nb; ++i) {
0808                         SKGAccountObject tmp(selection.at(i));
0809                         if (i != 0) {
0810                             wc += ',';
0811                             titleOpen += ',';
0812                         }
0813                         wc += SKGServices::intToString(tmp.getID());
0814                         titleOpen += i18n("'%1'", tmp.getDisplayName());
0815                     }
0816                     wc += ')';
0817                     // Set icon
0818                     iconOpen = QStringLiteral("view-bank-account");
0819                 }
0820             } else if (table == QStringLiteral("unit")) {
0821                 // Build whereclause and title
0822                 wc = QStringLiteral("rc_unit_id in (");
0823                 titleOpen = i18nc("Noun, a list of items", "Transactions with unit:");
0824 
0825                 for (int i = 0; i < nb; ++i) {
0826                     SKGUnitObject tmp(selection.at(i));
0827                     if (i != 0) {
0828                         wc += ',';
0829                         titleOpen += ',';
0830                     }
0831                     wc += SKGServices::intToString(tmp.getID());
0832                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0833                 }
0834                 wc += ')';
0835 
0836                 // Set icon
0837                 iconOpen = QStringLiteral("taxes-finances");
0838             } else if (table == QStringLiteral("category")) {
0839                 titleOpen = i18nc("Noun, a list of items", "Sub transactions with category:");
0840 
0841                 for (int i = 0; i < nb; ++i) {
0842                     SKGCategoryObject tmp(selection.at(i));
0843                     if (i != 0) {
0844                         wc += QStringLiteral(" OR ");
0845                         titleOpen += ',';
0846                     }
0847 
0848                     QString name = tmp.getFullName();
0849 
0850                     wc += QStringLiteral("(t_REALCATEGORY");
0851                     if (name.isEmpty()) {
0852                         wc += QStringLiteral(" IS NULL OR t_REALCATEGORY='')");
0853                     } else {
0854                         wc += " = '" % SKGServices::stringToSqlString(name) % "' OR t_REALCATEGORY like '" % SKGServices::stringToSqlString(name) % OBJECTSEPARATOR % "%')";
0855                     }
0856 
0857                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0858                 }
0859 
0860                 // Set icon
0861                 iconOpen = QStringLiteral("view-categories");
0862                 view = QStringLiteral("v_suboperation_consolidated");
0863             } else if (table == QStringLiteral("refund")) {
0864                 // Build whereclause and title
0865                 wc = QStringLiteral("t_REALREFUND in (");
0866                 titleOpen = i18nc("Noun, a list of items", "Sub transactions followed by tracker:");
0867 
0868                 for (int i = 0; i < nb; ++i) {
0869                     SKGTrackerObject tmp(selection.at(i));
0870                     if (i != 0) {
0871                         wc += ',';
0872                         titleOpen += ',';
0873                     }
0874                     wc += '\'' % SKGServices::stringToSqlString(tmp.getName()) % '\'';
0875                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0876                 }
0877                 wc += ')';
0878 
0879                 // Set icon
0880                 iconOpen = QStringLiteral("crosshairs");
0881                 view = QStringLiteral("v_suboperation_consolidated");
0882             } else if (table == QStringLiteral("payee")) {
0883                 // Build whereclause and title
0884                 wc = QStringLiteral("r_payee_id in (");
0885                 titleOpen = i18nc("Noun, a list of items", "Transactions assigned to payee:");
0886 
0887                 for (int i = 0; i < nb; ++i) {
0888                     SKGPayeeObject tmp(selection.at(i));
0889                     if (i != 0) {
0890                         wc += ',';
0891                         titleOpen += ',';
0892                     }
0893                     wc += SKGServices::intToString(tmp.getID());
0894                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0895                 }
0896                 wc += ')';
0897 
0898                 // Set icon
0899                 iconOpen = QStringLiteral("user-group-properties");
0900             } else if (table == QStringLiteral("budget")) {
0901                 titleOpen = i18nc("Noun, a list of items", "Transactions assigned to budget:");
0902 
0903                 for (int i = 0; i < nb; ++i) {
0904                     SKGBudgetObject tmp(selection.at(i));
0905                     if (i != 0) {
0906                         wc += QStringLiteral(" OR ");
0907                         titleOpen += ',';
0908                     }
0909 
0910                     wc += "(t_TYPEACCOUNT<>'L' AND i_SUBOPID IN (SELECT b2.id_suboperation FROM budgetsuboperation b2 WHERE b2.id=" % SKGServices::intToString(tmp.getID()) % "))";
0911                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0912                 }
0913 
0914                 // Set icon
0915                 iconOpen = QStringLiteral("view-calendar-whatsnext");
0916                 view = QStringLiteral("v_suboperation_consolidated");
0917             } else if (table == QStringLiteral("recurrentoperation")) {
0918                 titleOpen = i18nc("Noun, a list of items", "Scheduled transactions:");
0919 
0920                 for (int i = 0; i < nb; ++i) {
0921                     SKGRecurrentOperationObject tmp(selection.at(i));
0922                     if (i != 0) {
0923                         wc += QStringLiteral(" OR ");
0924                         titleOpen += ',';
0925                     }
0926 
0927                     wc += "(EXISTS(SELECT 1 FROM recurrentoperation s WHERE s.rd_operation_id=v_operation_display.id and s.id=" % SKGServices::intToString(tmp.getID()) % ") OR r_recurrentoperation_id=" % SKGServices::intToString(tmp.getID()) + ')';
0928                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0929                 }
0930 
0931                 // Set icon
0932                 iconOpen = QStringLiteral("chronometer");
0933                 view = QStringLiteral("v_operation_display");
0934             } else if (table == QStringLiteral("rule")) {
0935                 titleOpen = i18nc("Noun, a list of items", "Sub transactions corresponding to rule:");
0936 
0937                 for (int i = 0; i < nb; ++i) {
0938                     SKGRuleObject tmp(selection.at(i));
0939                     if (i != 0) {
0940                         wc += QStringLiteral(" OR ");
0941                         titleOpen += ',';
0942                     }
0943 
0944                     wc += "i_SUBOPID in (SELECT i_SUBOPID FROM v_operation_prop WHERE " % tmp.getSelectSqlOrder() % ')';
0945                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0946                 }
0947 
0948                 // Set icon
0949                 iconOpen = QStringLiteral("edit-find");
0950                 view = QStringLiteral("v_suboperation_consolidated");
0951             } else if (table == QStringLiteral("operation")) {
0952                 // Build whereclause and title
0953                 titleOpen = i18nc("Noun, a list of items", "Sub transactions grouped or split of:");
0954                 view = QStringLiteral("v_suboperation_consolidated");
0955 
0956                 for (int i = 0; i < nb; ++i) {
0957                     SKGOperationObject tmp(selection.at(i));
0958                     if (i != 0) {
0959                         wc += QStringLiteral(" OR ");
0960                         titleOpen += ',';
0961                     }
0962 
0963                     int opid = tmp.getID();
0964                     wc += QStringLiteral("(i_OPID=") % SKGServices::intToString(opid);
0965 
0966                     opid = SKGServices::stringToInt(tmp.getAttribute(QStringLiteral("i_group_id")));
0967                     if (opid != 0) {
0968                         wc += " or i_group_id=" % SKGServices::intToString(opid);
0969                     }
0970                     wc += ')';
0971 
0972                     titleOpen += i18n("'%1'", tmp.getDisplayName());
0973                 }
0974 
0975                 // Set icon
0976                 iconOpen = icon();
0977             } else if (table == QStringLiteral("suboperation")) {
0978                 // Build whereclause and title
0979                 titleOpen = i18nc("Noun, a list of items", "Transactions grouped with:");
0980                 view = QStringLiteral("v_operation_display_all");
0981 
0982                 for (int i = 0; i < nb; ++i) {
0983                     SKGSubOperationObject tmp(selection.at(i).getDocument(), selection.at(i).getID());
0984                     SKGOperationObject op;
0985                     tmp.getParentOperation(op);
0986                     if (i != 0) {
0987                         wc += QStringLiteral(" OR ");
0988                         titleOpen += ',';
0989                     }
0990 
0991                     int opid = op.getID();
0992                     wc += QStringLiteral("(id=") % SKGServices::intToString(opid);
0993 
0994                     opid = SKGServices::stringToInt(op.getAttribute(QStringLiteral("i_group_id")));
0995                     if (opid != 0) {
0996                         wc += " or i_group_id=" % SKGServices::intToString(opid);
0997                     }
0998                     wc += ')';
0999 
1000                     titleOpen += i18n("'%1'", op.getDisplayName());
1001                 }
1002 
1003                 // Set icon
1004                 iconOpen = icon();
1005             }
1006 
1007             // Open
1008             QDomDocument doc(QStringLiteral("SKGML"));
1009             doc.setContent(SKGMainPanel::getMainPanel()->getDocument()->getParameter(view != QStringLiteral("v_suboperation_consolidated") ? QStringLiteral("SKGOPERATION_DEFAULT_PARAMETERS") : QStringLiteral("SKGOPERATION_CONSOLIDATED_DEFAULT_PARAMETERS")));
1010             QDomElement root = doc.documentElement();
1011             if (root.isNull()) {
1012                 root = doc.createElement(QStringLiteral("parameters"));
1013                 doc.appendChild(root);
1014             }
1015 
1016             if (!view.isEmpty()) {
1017                 root.setAttribute(QStringLiteral("operationTable"), view);
1018             }
1019             if (!wc.isEmpty()) {
1020                 root.setAttribute(QStringLiteral("operationWhereClause"), wc);
1021             }
1022             if (!titleOpen.isEmpty()) {
1023                 root.setAttribute(QStringLiteral("title"), titleOpen);
1024             }
1025             if (!iconOpen.isEmpty()) {
1026                 root.setAttribute(QStringLiteral("title_icon"), iconOpen);
1027             }
1028             if (!account.isEmpty()) {
1029                 root.setAttribute(QStringLiteral("account"), account);
1030             } else {
1031                 root.setAttribute(QStringLiteral("currentPage"), QStringLiteral("-1"));
1032             }
1033 
1034             SKGMainPanel::getMainPanel()->openPage(SKGMainPanel::getMainPanel()->getPluginByName(QStringLiteral("Skrooge operation plugin")), -1, doc.toString(), view == QStringLiteral("v_suboperation_consolidated") ? i18nc("Noun, a list of items", "Sub transactions") : QString());
1035         }
1036     }
1037 }
1038 
1039 SKGAdviceList SKGOperationPlugin::advice(const QStringList& iIgnoredAdvice)
1040 {
1041     SKGTRACEINFUNC(10)
1042     SKGAdviceList output;
1043     output.reserve(20);
1044     int nbConcurrentCheckExecuted = 0;
1045     int nbConcurrentCheckTargeted = 0;
1046     QMutex mutex;
1047 
1048     // Search duplicate number on operation
1049     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_duplicate"))) {
1050         nbConcurrentCheckTargeted++;
1051         m_currentBankDocument->concurrentExecuteSelectSqliteOrder(QStringLiteral("SELECT count(1), t_ACCOUNT, t_number FROM v_operation WHERE t_number!='' GROUP BY t_ACCOUNT, t_number HAVING count(1)>1 ORDER BY count(1) DESC"), [ & ](const SKGStringListList & iResult) {
1052             int nb = iResult.count();
1053             SKGAdvice::SKGAdviceActionList autoCorrections;
1054             for (int i = 1; i < nb; ++i) {  // Ignore header
1055                 // Get parameters
1056                 const QStringList& line = iResult.at(i);
1057                 const QString& account = line.at(1);
1058                 const QString& number = line.at(2);
1059 
1060                 SKGAdvice ad;
1061                 ad.setUUID("skgoperationplugin_duplicate|" % number % ';' % account);
1062                 ad.setPriority(7);
1063                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Duplicate number %1 in account '%2'", number, account));
1064                 ad.setLongMessage(i18nc("Advice on making the best (long)", "Your account '%1' contains more than one transaction with number %2.The transaction number should be unique (check number, transaction reference…)", account, number));
1065                 autoCorrections.resize(0);
1066                 {
1067                     SKGAdvice::SKGAdviceAction a;
1068                     a.Title = i18nc("Advice on making the best (action)", "Edit transactions with duplicate number");
1069                     a.IconName = QStringLiteral("quickopen");
1070                     a.IsRecommended = false;
1071                     autoCorrections.push_back(a);
1072                 }
1073                 ad.setAutoCorrections(autoCorrections);
1074                 mutex.lock();
1075                 output.push_back(ad);
1076                 mutex.unlock();
1077             }
1078             mutex.lock();
1079             nbConcurrentCheckExecuted++;
1080             mutex.unlock();
1081         }, false);
1082     }
1083 
1084     // Check transactions not reconciled
1085     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_notreconciled"))) {
1086         nbConcurrentCheckTargeted++;
1087         m_currentBankDocument->concurrentExecuteSelectSqliteOrder(QStringLiteral("SELECT count(1), t_ACCOUNT FROM v_operation WHERE t_status='N' GROUP BY t_ACCOUNT HAVING count(1)>100 ORDER BY count(1) DESC"), [ & ](const SKGStringListList & iResult) {
1088             int nb = iResult.count();
1089             SKGAdvice::SKGAdviceActionList autoCorrections;
1090             for (int i = 1; i < nb; ++i) {  // Ignore header
1091                 // Get parameters
1092                 const QStringList& line = iResult.at(i);
1093                 const QString& account = line.at(1);
1094 
1095                 SKGAdvice ad;
1096                 ad.setUUID("skgoperationplugin_notreconciled|" % account);
1097                 ad.setPriority(9);
1098                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transactions of '%1' not reconciled", account));
1099                 ad.setLongMessage(i18nc("Advice on making the best (long)", "Do not forget to reconcile your accounts. By doing so, you acknowledge that your bank has indeed processed these transactions on your account. This is how you enforce compliance with your bank's statements. See online help for more details"));
1100                 autoCorrections.resize(0);
1101                 {
1102                     SKGAdvice::SKGAdviceAction a;
1103                     a.Title = i18nc("Advice on making the best (action)", "Open account '%1' for reconciliation", account);
1104                     a.IconName = QStringLiteral("quickopen");
1105                     a.IsRecommended = false;
1106                     autoCorrections.push_back(a);
1107                 }
1108                 ad.setAutoCorrections(autoCorrections);
1109                 mutex.lock();
1110                 output.push_back(ad);
1111                 mutex.unlock();
1112             }
1113             mutex.lock();
1114             nbConcurrentCheckExecuted++;
1115             mutex.unlock();
1116         }, false);
1117     }
1118 
1119     // Not enough money in your account
1120     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_minimum_limit"))) {
1121         nbConcurrentCheckTargeted++;
1122         // Get accounts with amount close or below the minimum limit
1123         m_currentBankDocument->concurrentExecuteSelectSqliteOrder(QStringLiteral("SELECT t_name FROM v_account_amount WHERE t_close='N' AND t_minamount_enabled='Y' AND f_CURRENTAMOUNT<f_CURRENTAMOUNTUNIT*f_minamount"), [ & ](const SKGStringListList & iResult) {
1124             int nb = iResult.count();
1125             mutex.lock();
1126             output.reserve(output.count() + nb);
1127             mutex.unlock();
1128             for (int i = 1; i < nb; ++i) {  // Ignore header
1129                 // Get parameters
1130                 QString account = iResult.at(i).at(0);
1131 
1132                 SKGAdvice ad;
1133                 ad.setUUID("skgoperationplugin_minimum_limit|" % account);
1134                 ad.setPriority(9);
1135                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Not enough money in your account '%1'", account));
1136                 ad.setLongMessage(i18nc("Advice on making the best (long)", "The amount of this account is below the minimum limit. You should replenish it."));
1137                 mutex.lock();
1138                 output.push_back(ad);
1139                 mutex.unlock();
1140             }
1141             mutex.lock();
1142             nbConcurrentCheckExecuted++;
1143             mutex.unlock();
1144         }, false);
1145     }
1146 
1147     // Maximum limit reached
1148     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_maximum_limit"))) {
1149         nbConcurrentCheckTargeted++;
1150         // Get accounts with amount over the maximum limit
1151         m_currentBankDocument->concurrentExecuteSelectSqliteOrder(QStringLiteral("SELECT t_name FROM v_account_amount WHERE t_close='N' AND t_maxamount_enabled='Y' AND f_CURRENTAMOUNT>f_CURRENTAMOUNTUNIT*f_maxamount"),  [ & ](const SKGStringListList & iResult) {
1152             int nb = iResult.count();
1153             mutex.lock();
1154             output.reserve(output.count() + nb);
1155             mutex.unlock();
1156             for (int i = 1; i < nb; ++i) {  // Ignore header
1157                 // Get parameters
1158                 QString account = iResult.at(i).at(0);
1159 
1160                 SKGAdvice ad;
1161                 ad.setUUID("skgoperationplugin_maximum_limit|" % account);
1162                 ad.setPriority(6);
1163                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Balance in account '%1' exceeds the maximum limit", account));
1164                 ad.setLongMessage(i18nc("Advice on making the best (long)", "The balance of this account exceeds the maximum limit."));
1165                 mutex.lock();
1166                 output.push_back(ad);
1167                 mutex.unlock();
1168             }
1169             mutex.lock();
1170             nbConcurrentCheckExecuted++;
1171             mutex.unlock();
1172         }, false);
1173     }
1174 
1175     // Minimum limit reached
1176     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_close_minimum_limit"))) {
1177         nbConcurrentCheckTargeted++;
1178         // Get accounts with amount close or below the minimum limit
1179         m_currentBankDocument->concurrentExecuteSelectSqliteOrder(QStringLiteral("SELECT t_name FROM v_account_amount WHERE t_close='N' AND t_minamount_enabled='Y' AND t_maxamount_enabled='Y'"
1180         " AND f_CURRENTAMOUNT>=f_CURRENTAMOUNTUNIT*f_minamount AND f_CURRENTAMOUNT<=f_CURRENTAMOUNTUNIT*f_minamount+0.1*(f_CURRENTAMOUNTUNIT*f_maxamount-f_CURRENTAMOUNTUNIT*f_minamount)"),  [ & ](const SKGStringListList & iResult) {
1181             int nb = iResult.count();
1182             mutex.lock();
1183             output.reserve(output.count() + nb);
1184             mutex.unlock();
1185             for (int i = 1; i < nb; ++i) {  // Ignore header
1186                 // Get parameters
1187                 QString account = iResult.at(i).at(0);
1188 
1189                 SKGAdvice ad;
1190                 ad.setUUID("skgoperationplugin_close_minimum_limit|" % account);
1191                 ad.setPriority(5);
1192                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Your account '%1' is close to the minimum limit", account));
1193                 ad.setLongMessage(i18nc("Advice on making the best (long)", "The amount of this account is close to the minimum limit. You should take care of it."));
1194                 mutex.lock();
1195                 output.push_back(ad);
1196                 mutex.unlock();
1197             }
1198             mutex.lock();
1199             nbConcurrentCheckExecuted++;
1200             mutex.unlock();
1201         }, false);
1202     }
1203 
1204     // Too much money in your account
1205     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_too_much_money"))) {
1206         nbConcurrentCheckTargeted++;
1207         m_currentBankDocument->concurrentExecuteSelectSqliteOrder(QStringLiteral("SELECT t_name, f_RATE FROM v_account_display WHERE t_close='N' AND f_RATE>0 AND (t_maxamount_enabled='N' OR f_CURRENTAMOUNT<f_CURRENTAMOUNTUNIT*f_maxamount*0.9) ORDER BY f_RATE DESC"), [ & ](const SKGStringListList & iResult) {
1208             int nb = iResult.count();
1209             if (nb > 1) {
1210                 // Get better interest account
1211                 QString target = iResult.at(1).at(0);
1212                 QString rate = iResult.at(1).at(1);
1213 
1214                 // Get accounts with too much money
1215                 m_currentBankDocument->concurrentExecuteSelectSqliteOrder("SELECT t_name FROM v_account_display WHERE t_close='N' AND ("
1216                         // Interest are computed on amount over the limit too, not need to transfer them "(t_maxamount_enabled='Y' AND f_CURRENTAMOUNT>f_CURRENTAMOUNTUNIT*f_maxamount) OR "
1217                         "(f_RATE<" % rate % " AND t_type='C' AND f_CURRENTAMOUNT>-2*(SELECT TOTAL(s.f_CURRENTAMOUNT) FROM v_operation_display s WHERE s.rd_account_id=v_account_display.id AND s.t_TYPEEXPENSE='-' AND s.d_DATEMONTH = (SELECT strftime('%Y-%m',date('now', 'localtime','start of month', '-1 MONTH')))))"
1218                 ")", [ &output, target, rate ](const SKGStringListList & iResult2) {
1219                     int nb = iResult2.count();
1220                     QMutex mutex;
1221                     mutex.lock();
1222                     output.reserve(output.count() + nb);
1223                     mutex.unlock();
1224                     for (int i = 1; i < nb; ++i) {  // Ignore header
1225                         // Get parameters
1226                         QString account = iResult2.at(i).at(0);
1227 
1228                         SKGAdvice ad;
1229                         ad.setUUID("skgoperationplugin_too_much_money|" % account);
1230                         ad.setPriority(6);
1231                         ad.setShortMessage(i18nc("Advice on making the best (short)", "Too much money in your account '%1'", account));
1232                         ad.setLongMessage(i18nc("Advice on making the best (long)", "You could save money on an account with a better rate. Example: '%1' (%2%)", target, rate));
1233                         mutex.lock();
1234                         output.push_back(ad);
1235                         mutex.unlock();
1236                     }
1237                 }, false);
1238             }
1239             mutex.lock();
1240             nbConcurrentCheckExecuted++;
1241             mutex.unlock();
1242         }, false);
1243     }
1244 
1245     // Check transactions without mode
1246     if (!iIgnoredAdvice.contains(QStringLiteral("skgimportexportplugin_notvalidated"))) {
1247         nbConcurrentCheckTargeted++;
1248         m_currentBankDocument->concurrentExistObjects(QStringLiteral("operation"),
1249                 QStringLiteral("t_template='N' AND t_mode='' AND d_date<>'0000-00-00'"),
1250         [ & ](bool iFound) {
1251             if (iFound) {
1252                 SKGAdvice ad;
1253                 ad.setUUID(QStringLiteral("skgimportexportplugin_notvalidated"));
1254                 ad.setPriority(5);
1255                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transactions do not have mode"));
1256                 ad.setLongMessage(i18nc("Advice on making the best (long)", "Do not forget to set a mode for each transaction. This will allow you to generate better reports."));
1257                 QStringList autoCorrections;
1258                 autoCorrections.push_back(QStringLiteral("skg://view_open_operation_without_mode"));
1259                 ad.setAutoCorrections(autoCorrections);
1260 
1261                 mutex.lock();
1262                 output.push_back(ad);
1263                 mutex.unlock();
1264             }
1265             mutex.lock();
1266             nbConcurrentCheckExecuted++;
1267             mutex.unlock();
1268         }, false);
1269     }
1270 
1271     // Check transactions without category
1272     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_nocategory"))) {
1273         nbConcurrentCheckTargeted++;
1274         m_currentBankDocument->concurrentExistObjects(QStringLiteral("v_operation_display"),
1275                 QStringLiteral("t_TRANSFER='N' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)"),
1276         [ & ](bool iFound) {
1277             if (iFound) {
1278                 SKGAdvice ad;
1279                 ad.setUUID(QStringLiteral("skgoperationplugin_nocategory"));
1280                 ad.setPriority(5);
1281                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transactions do not have category"));
1282                 ad.setLongMessage(i18nc("Advice on making the best (long)", "Do not forget to associate a category for each transaction. This will allow you to generate better reports."));
1283                 QStringList autoCorrections;
1284                 autoCorrections.push_back(QStringLiteral("skg://view_open_operation_without_category"));
1285                 ad.setAutoCorrections(autoCorrections);
1286 
1287                 mutex.lock();
1288                 output.push_back(ad);
1289                 mutex.unlock();
1290             }
1291             mutex.lock();
1292             nbConcurrentCheckExecuted++;
1293             mutex.unlock();
1294         }, false);
1295     }
1296 
1297     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_transfer_nocategory"))) {
1298         nbConcurrentCheckTargeted++;
1299         m_currentBankDocument->concurrentExistObjects(QStringLiteral("v_operation_display"),
1300                 QStringLiteral("t_TRANSFER='Y' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)"),
1301         [ & ](bool iFound) {
1302             if (iFound) {
1303                 SKGAdvice ad;
1304                 ad.setUUID(QStringLiteral("skgoperationplugin_transfer_nocategory"));
1305                 ad.setPriority(3);
1306                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transfers do not have category"));
1307                 ad.setLongMessage(i18nc("Advice on making the best (long)", "Do not forget to associate a category for each transfer."));
1308                 QStringList autoCorrections;
1309                 autoCorrections.push_back(QStringLiteral("skg://view_open_transfers_without_category"));
1310                 ad.setAutoCorrections(autoCorrections);
1311 
1312                 mutex.lock();
1313                 output.push_back(ad);
1314                 mutex.unlock();
1315             }
1316             mutex.lock();
1317             nbConcurrentCheckExecuted++;
1318             mutex.unlock();
1319         }, false);
1320     }
1321 
1322     // Check transactions without payee
1323     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_nopayee"))) {
1324         nbConcurrentCheckTargeted++;
1325         m_currentBankDocument->concurrentExistObjects(QStringLiteral("v_operation_display"),
1326                 QStringLiteral("t_TRANSFER='N' AND r_payee_id=0"),
1327         [ & ](bool iFound) {
1328             if (iFound) {
1329                 SKGAdvice ad;
1330                 ad.setUUID(QStringLiteral("skgoperationplugin_nopayee"));
1331                 ad.setPriority(5);
1332                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transactions do not have payee"));
1333                 ad.setLongMessage(i18nc("Advice on making the best (long)", "Do not forget to associate a payee for each transaction. This will allow you to generate better reports."));
1334                 QStringList autoCorrections;
1335                 autoCorrections.push_back(QStringLiteral("skg://view_open_operation_without_payee"));
1336                 ad.setAutoCorrections(autoCorrections);
1337 
1338                 mutex.lock();
1339                 output.push_back(ad);
1340                 mutex.unlock();
1341             }
1342             mutex.lock();
1343             nbConcurrentCheckExecuted++;
1344             mutex.unlock();
1345         }, false);
1346     }
1347 
1348     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_transfer_nopayee"))) {
1349         nbConcurrentCheckTargeted++;
1350         m_currentBankDocument->concurrentExistObjects(QStringLiteral("v_operation_display"),
1351                 QStringLiteral("t_TRANSFER='Y' AND r_payee_id=0"),
1352         [ & ](bool iFound) {
1353             if (iFound) {
1354                 SKGAdvice ad;
1355                 ad.setUUID(QStringLiteral("skgoperationplugin_transfer_nopayee"));
1356                 ad.setPriority(3);
1357                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transfers do not have payee"));
1358                 ad.setLongMessage(i18nc("Advice on making the best (long)", "Do not forget to associate a payee for each transfer."));
1359                 QStringList autoCorrections;
1360                 autoCorrections.push_back(QStringLiteral("skg://view_open_transfers_without_payee"));
1361                 ad.setAutoCorrections(autoCorrections);
1362 
1363                 mutex.lock();
1364                 output.push_back(ad);
1365                 mutex.unlock();
1366             }
1367             mutex.lock();
1368             nbConcurrentCheckExecuted++;
1369             mutex.unlock();
1370         }, false);
1371     }
1372 
1373     // Check transactions in group of only one transaction
1374     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_groupofone"))) {
1375         nbConcurrentCheckTargeted++;
1376         m_currentBankDocument->concurrentExistObjects(QStringLiteral("v_operation_display"),
1377                 QStringLiteral("i_group_id<>0 AND (SELECT COUNT(1) FROM operation o WHERE o.i_group_id=v_operation_display.i_group_id)<2"),
1378         [ & ](bool iFound) {
1379             if (iFound) {
1380                 SKGAdvice ad;
1381                 ad.setUUID(QStringLiteral("skgoperationplugin_groupofone"));
1382                 ad.setPriority(4);
1383                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Some transactions are in groups with only one transaction"));
1384                 ad.setLongMessage(i18nc("Advice on making the best (long)", "When a transfer is created and when only one part of this transfer is removed, the second part is in a group of only one transaction. This makes no sense."));
1385                 SKGAdvice::SKGAdviceActionList autoCorrections;
1386                 {
1387                     SKGAdvice::SKGAdviceAction a;
1388                     a.Title = QStringLiteral("skg://view_open_operation_in_group_of_one");
1389                     a.IsRecommended = false;
1390                     autoCorrections.push_back(a);
1391                 }
1392                 {
1393                     SKGAdvice::SKGAdviceAction a;
1394                     a.Title = QStringLiteral("skg://clean_remove_group_with_one_operation");
1395                     a.IsRecommended = true;
1396                     autoCorrections.push_back(a);
1397                 }
1398                 ad.setAutoCorrections(autoCorrections);
1399 
1400                 mutex.lock();
1401                 output.push_back(ad);
1402                 mutex.unlock();
1403             }
1404             mutex.lock();
1405             nbConcurrentCheckExecuted++;
1406             mutex.unlock();
1407         }, false);
1408     }
1409 
1410     // Check simple transactions with comment not aligned with the comment of the subtransaction
1411     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_commentsnotaligned"))) {
1412         nbConcurrentCheckTargeted++;
1413         m_currentBankDocument->concurrentExistObjects(QStringLiteral("operation op, suboperation so"),
1414                 QStringLiteral("so.rd_operation_id=op.id AND so.t_comment<>op.t_comment AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1"),
1415         [ & ](bool iFound) {
1416             if (iFound) {
1417                 SKGAdvice ad;
1418                 ad.setUUID(QStringLiteral("skgoperationplugin_commentsnotaligned"));
1419                 ad.setPriority(5);
1420                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Some simple transactions do not have their comments aligned"));
1421                 ad.setLongMessage(i18nc("Advice on making the best (long)", "The comment of the transaction is not aligned with the comment of the subtransaction."));
1422                 SKGAdvice::SKGAdviceActionList autoCorrections;
1423                 {
1424                     SKGAdvice::SKGAdviceAction a;
1425                     a.Title = QStringLiteral("skg://view_open_operation_with_comment_not_aligned");
1426                     a.IsRecommended = false;
1427                     autoCorrections.push_back(a);
1428                 }
1429                 {
1430                     SKGAdvice::SKGAdviceAction a;
1431                     a.Title = QStringLiteral("skg://align_comment");
1432                     a.IsRecommended = true;
1433                     autoCorrections.push_back(a);
1434                 }
1435                 {
1436                     SKGAdvice::SKGAdviceAction a;
1437                     a.Title = QStringLiteral("skg://align_comment2");
1438                     a.IsRecommended = false;
1439                     autoCorrections.push_back(a);
1440                 }
1441                 ad.setAutoCorrections(autoCorrections);
1442 
1443                 mutex.lock();
1444                 output.push_back(ad);
1445                 mutex.unlock();
1446             }
1447             mutex.lock();
1448             nbConcurrentCheckExecuted++;
1449             mutex.unlock();
1450         }, false);
1451     }
1452 
1453     // Check simple transactions with date not aligned with the date of the subtransaction
1454     if (!iIgnoredAdvice.contains(QStringLiteral("skgoperationplugin_datesnotaligned"))) {
1455         nbConcurrentCheckTargeted++;
1456         m_currentBankDocument->concurrentExistObjects(QStringLiteral("operation op, suboperation so"),
1457                 QStringLiteral("so.rd_operation_id=op.id AND (so.d_date<op.d_date OR (so.d_date<>op.d_date AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1))"),
1458         [ & ](bool iFound) {
1459             if (iFound) {
1460                 SKGAdvice ad;
1461                 ad.setUUID(QStringLiteral("skgoperationplugin_datesnotaligned"));
1462                 ad.setPriority(5);
1463                 ad.setShortMessage(i18nc("Advice on making the best (short)", "Some transactions do not have their dates aligned"));
1464                 ad.setLongMessage(i18nc("Advice on making the best (long)", "The date of the transaction is not aligned with the date of the subtransaction. This case seems to be abnormal."));
1465                 SKGAdvice::SKGAdviceActionList autoCorrections;
1466                 {
1467                     SKGAdvice::SKGAdviceAction a;
1468                     a.Title = QStringLiteral("skg://view_open_operation_with_date_not_aligned");
1469                     a.IsRecommended = false;
1470                     autoCorrections.push_back(a);
1471                 }
1472                 {
1473                     SKGAdvice::SKGAdviceAction a;
1474                     a.Title = QStringLiteral("skg://align_date");
1475                     a.IsRecommended = true;
1476                     autoCorrections.push_back(a);
1477                 }
1478                 ad.setAutoCorrections(autoCorrections);
1479 
1480                 mutex.lock();
1481                 output.push_back(ad);
1482                 mutex.unlock();
1483             }
1484             mutex.lock();
1485             nbConcurrentCheckExecuted++;
1486             mutex.unlock();
1487         }, false);
1488     }
1489 
1490     // Check transactions having category not aligned with the category of the payee
1491     if (!iIgnoredAdvice.contains(QStringLiteral("skgpayeeplugin_subop_with_wrong_wategory"))) {
1492         bool exist = false;
1493         m_currentBankDocument->existObjects(QStringLiteral("v_suboperation_consolidated INNER JOIN payee ON v_suboperation_consolidated.r_payee_id=payee.id"), QStringLiteral("payee.r_category_id!=0 AND v_suboperation_consolidated.r_category_id!=payee.r_category_id"), exist);
1494         if (exist) {
1495             SKGAdvice ad;
1496             ad.setUUID(QStringLiteral("skgpayeeplugin_subop_with_wrong_wategory"));
1497             ad.setPriority(3);
1498             ad.setShortMessage(i18nc("Advice on making the best (short)", "Category not aligned with payee's category"));
1499             ad.setLongMessage(i18nc("Advice on making the best (long)", "Some transactions don't have the category aligned with the category of the payee."));
1500             SKGAdvice::SKGAdviceActionList autoCorrections;
1501             {
1502                 SKGAdvice::SKGAdviceAction a;
1503                 a.Title = QStringLiteral("skg://view_open_suboperations_without_payee_category");
1504                 autoCorrections.push_back(a);
1505             }
1506             {
1507                 SKGAdvice::SKGAdviceAction a;
1508                 a.Title = QStringLiteral("skg://align_category");
1509 
1510                 autoCorrections.push_back(a);
1511             }
1512             ad.setAutoCorrections(autoCorrections);
1513             output.push_back(ad);
1514         }
1515     }
1516 
1517     do {
1518         QThread::yieldCurrentThread();
1519         if (nbConcurrentCheckExecuted == nbConcurrentCheckTargeted) {
1520             break;
1521         }
1522     } while (true);
1523 
1524     return output;
1525 }
1526 
1527 SKGError SKGOperationPlugin::executeAdviceCorrection(const QString& iAdviceIdentifier, int iSolution)
1528 {
1529     if ((m_currentBankDocument != nullptr) && iAdviceIdentifier.startsWith(QLatin1String("skgoperationplugin_duplicate|"))) {
1530         // Get parameters
1531         QString parameters = iAdviceIdentifier.right(iAdviceIdentifier.length() - 29);
1532         int pos = parameters.indexOf(';');
1533         QString num = parameters.left(pos);
1534         QString account = parameters.right(parameters.length() - 1 - pos);
1535 
1536         // Call transaction plugin
1537         SKGMainPanel::getMainPanel()->openPage("skg://skrooge_operation_plugin/?title_icon=security-low&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transactions of '%1' with duplicate number %2", account, num)) %
1538                                                "&operationWhereClause=" % SKGServices::encodeForUrl("t_number='" % SKGServices::stringToSqlString(num) % "' AND t_ACCOUNT='" % SKGServices::stringToSqlString(account) % '\''));
1539         return SKGError();
1540     }
1541     if ((m_currentBankDocument != nullptr) && iAdviceIdentifier.startsWith(QLatin1String("skgoperationplugin_notreconciled|"))) {
1542         // Get parameters
1543         QString account = iAdviceIdentifier.right(iAdviceIdentifier.length() - 36);
1544         SKGMainPanel::getMainPanel()->openPage("skg://skrooge_operation_plugin/?currentPage=-1&modeInfoZone=1&account=" % SKGServices::encodeForUrl(account));
1545         return SKGError();
1546     }
1547 
1548     return SKGInterfacePlugin::executeAdviceCorrection(iAdviceIdentifier, iSolution);
1549 }
1550 
1551 void SKGOperationPlugin::onRemoveGroupWithOneOperation()
1552 {
1553     SKGError err;
1554     SKGTRACEINFUNCRC(10, err) {
1555         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
1556         auto* act = qobject_cast< QAction* >(sender());
1557         if ((act == nullptr) || !act->data().toBool()) {
1558             // Clear selection to enable the model "All"
1559             selection.clear();
1560         }
1561         SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Remove groups with only one transaction"), err)
1562         QString q = QStringLiteral("UPDATE operation SET i_group_id=0 WHERE i_group_id<>0 AND (SELECT COUNT(1) FROM operation o WHERE o.i_group_id=operation.i_group_id)<2");
1563         int nb = selection.count();
1564         if (nb == 0) {
1565             err = m_currentBankDocument->executeSqliteOrder(q);
1566         } else {
1567             for (int i = 0; !err && i < nb; ++i) {
1568                 SKGOperationObject op(selection.at(i));
1569                 err = m_currentBankDocument->executeSqliteOrder(q % " AND id=" % SKGServices::intToString(op.getID()));
1570             }
1571         }
1572     }
1573 
1574     // status bar
1575     IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Remove groups done.")))
1576     else {
1577         err.addError(ERR_FAIL, i18nc("Error message", "Remove groups failed"));
1578     }
1579 
1580     // Display error
1581     SKGMainPanel::displayErrorMessage(err);
1582 }
1583 
1584 void SKGOperationPlugin::onAlignComment()
1585 {
1586     SKGError err;
1587     SKGTRACEINFUNCRC(10, err)
1588 
1589     {
1590         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
1591         auto* act = qobject_cast< QAction* >(sender());
1592         if ((act == nullptr) || !act->data().toBool()) {
1593             // Clear selection to enable the model "All"
1594             selection.clear();
1595         }
1596         SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Align comment of subtransactions"), err)
1597         QString q = QStringLiteral("UPDATE suboperation SET t_comment=(SELECT op.t_comment FROM operation op WHERE suboperation.rd_operation_id=op.id) WHERE suboperation.id IN (SELECT so.id FROM operation op, suboperation so WHERE so.rd_operation_id=op.id AND so.t_comment<>op.t_comment AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1)");
1598         int nb = selection.count();
1599         if (nb == 0) {
1600             err = m_currentBankDocument->executeSqliteOrder(q);
1601         } else {
1602             for (int i = 0; !err && i < nb; ++i) {
1603                 SKGOperationObject op(selection.at(i));
1604                 err = m_currentBankDocument->executeSqliteOrder(q % " AND rd_operation_id=" % SKGServices::intToString(op.getID()));
1605             }
1606         }
1607     }
1608 
1609     // status bar
1610     IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Comments aligned.")))
1611     else {
1612         err.addError(ERR_FAIL, i18nc("Error message", "Comments alignment failed"));
1613     }
1614 
1615     // Display error
1616     SKGMainPanel::displayErrorMessage(err);
1617 }
1618 
1619 void SKGOperationPlugin::onAlignComment2()
1620 {
1621     SKGError err;
1622     SKGTRACEINFUNCRC(10, err)
1623 
1624     {
1625         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
1626         auto* act = qobject_cast< QAction* >(sender());
1627         if ((act == nullptr) || !act->data().toBool()) {
1628             // Clear selection to enable the model "All"
1629             selection.clear();
1630         }
1631         SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Align comment of transactions"), err)
1632         QString q = QStringLiteral("UPDATE operation SET t_comment=(SELECT suboperation.t_comment FROM suboperation WHERE suboperation.rd_operation_id=operation.id) WHERE operation.id IN (SELECT operation.id FROM operation op, suboperation so WHERE so.rd_operation_id=op.id AND so.t_comment<>op.t_comment AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1)");
1633         int nb = selection.count();
1634         if (nb == 0) {
1635             err = m_currentBankDocument->executeSqliteOrder(q);
1636         } else {
1637             for (int i = 0; !err && i < nb; ++i) {
1638                 SKGOperationObject op(selection.at(i));
1639                 err = m_currentBankDocument->executeSqliteOrder(q % " AND id=" % SKGServices::intToString(op.getID()));
1640             }
1641         }
1642     }
1643 
1644     // status bar
1645     IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Comments aligned.")))
1646     else {
1647         err.addError(ERR_FAIL, i18nc("Error message", "Comments alignment failed"));
1648     }
1649 
1650     // Display error
1651     SKGMainPanel::displayErrorMessage(err);
1652 }
1653 
1654 void SKGOperationPlugin::onAlignDate()
1655 {
1656     SKGError err;
1657     SKGTRACEINFUNCRC(10, err)
1658 
1659     {
1660         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
1661         auto* act = qobject_cast< QAction* >(sender());
1662         if ((act == nullptr) || !act->data().toBool()) {
1663             // Clear selection to enable the model "All"
1664             selection.clear();
1665         }
1666         SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Align date of subtransactions"), err)
1667         QString q = QStringLiteral("UPDATE suboperation SET d_date=(SELECT op.d_date FROM operation op WHERE suboperation.rd_operation_id=op.id) WHERE suboperation.id IN (SELECT so.id FROM operation op, suboperation so WHERE so.rd_operation_id=op.id AND (so.d_date<op.d_date OR (so.d_date<>op.d_date AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1)))");
1668         int nb = selection.count();
1669         if (nb == 0) {
1670             err = m_currentBankDocument->executeSqliteOrder(q);
1671         } else {
1672             for (int i = 0; !err && i < nb; ++i) {
1673                 SKGOperationObject op(selection.at(i));
1674                 err = m_currentBankDocument->executeSqliteOrder(q % " AND rd_operation_id=" % SKGServices::intToString(op.getID()));
1675             }
1676         }
1677     }
1678 
1679     // status bar
1680     IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Dates aligned.")))
1681     else {
1682         err.addError(ERR_FAIL, i18nc("Error message", "Dates alignment failed"));
1683     }
1684 
1685     // Display error
1686     SKGMainPanel::displayErrorMessage(err);
1687 }
1688 
1689 void SKGOperationPlugin::onAlignWithCategoryOfPayee()
1690 {
1691     SKGError err;
1692     SKGTRACEINFUNCRC(10, err)
1693 
1694     {
1695         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
1696         auto* act = qobject_cast< QAction* >(sender());
1697         if ((act == nullptr) || !act->data().toBool()) {
1698             // Clear selection to enable the model "All"
1699             selection.clear();
1700         }
1701         SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Align the category of all single transactions with the category of their payee"), err)
1702         QString q = QStringLiteral("UPDATE suboperation SET r_category_id=(SELECT p.r_category_id FROM v_suboperation_consolidated so INNER JOIN payee p ON so.r_payee_id=p.id WHERE so.id=suboperation.id AND p.r_category_id!=0 AND so.r_category_id!=p.r_category_id) WHERE suboperation.id IN (SELECT so.id FROM v_suboperation_consolidated so INNER JOIN payee p ON so.r_payee_id=p.id WHERE p.r_category_id!=0 AND so.r_category_id!=p.r_category_id AND so.i_NBSUBOPERATIONS)");
1703         int nb = selection.count();
1704         if (nb == 0) {
1705             err = m_currentBankDocument->executeSqliteOrder(q);
1706         } else {
1707             for (int i = 0; !err && i < nb; ++i) {
1708                 SKGOperationObject op(selection.at(i));
1709                 err = m_currentBankDocument->executeSqliteOrder(q % " AND rd_operation_id=" % SKGServices::intToString(op.getID()));
1710             }
1711         }
1712     }
1713 
1714     // status bar
1715     IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Categories aligned.")))
1716     else {
1717         err.addError(ERR_FAIL, i18nc("Error message", "Categories alignment failed"));
1718     }
1719 
1720     // Display error
1721     SKGMainPanel::displayErrorMessage(err);
1722 }
1723 
1724 void SKGOperationPlugin::onMergeSubOperations()
1725 {
1726     SKGError err;
1727     SKGTRACEINFUNCRC(10, err)
1728 
1729     if ((SKGMainPanel::getMainPanel() != nullptr) && (m_currentBankDocument != nullptr)) {
1730         SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
1731         int nb = selection.count();
1732         if (nb >= 2) {
1733             SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Merge sub transactions"), err)
1734             SKGOperationObject op(selection.at(0));
1735             for (int i = 1; !err && i < nb; ++i) {
1736                 SKGOperationObject op2(selection.at(i));
1737                 err = op.mergeSuboperations(op2);
1738 
1739                 // Send message
1740                 IFOKDO(err, op.getDocument()->sendMessage(i18nc("An information to the user", "The sub transactions of '%1' have been merged in the transaction '%2'", op2.getDisplayName(), op.getDisplayName()), SKGDocument::Hidden))
1741             }
1742         }
1743     }
1744 
1745     // status bar
1746     IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Transactions merged.")))
1747     else {
1748         err.addError(ERR_FAIL, i18nc("Error message", "Merge failed"));
1749     }
1750 
1751     // Display error
1752     SKGMainPanel::displayErrorMessage(err);
1753 }
1754 
1755 void SKGOperationPlugin::onShowOpenWithMenu()
1756 {
1757     if ((m_openOperationsWithMenu != nullptr) && (m_currentBankDocument != nullptr)) {
1758         // Clean Menu
1759         auto* m = qobject_cast<QMenu*>(sender());
1760         bool modeOperation = (m == m_openOperationsWithMenu);
1761         m->clear();
1762 
1763         // Get Selection
1764         auto selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
1765 
1766         // Build list od attribute to take into account
1767         QStringList existingAttributes;
1768         m_currentBankDocument->getAttributesList(modeOperation ? QStringLiteral("v_operation_display") : QStringLiteral("v_suboperation_consolidated"), existingAttributes);
1769 
1770         QStringList attributes;
1771         auto schema = SKGServices::splitCSVLine(m_currentBankDocument->getDisplaySchemas(modeOperation ? QStringLiteral("operation") : QStringLiteral("suboperation")).at(0).schema);
1772         attributes.reserve(attributes.count());
1773         for (const auto& att : qAsConst(schema)) {
1774             auto a = SKGServices::splitCSVLine(att, QLatin1Char('|')).value(0);
1775             if (existingAttributes.contains(a) && !a.endsWith(QLatin1String("_INCOME")) && !a.endsWith(QLatin1String("_EXPENSE")))  {
1776                 attributes.push_back(a);
1777             }
1778         }
1779 
1780         // Build menus
1781         auto unitp = m_currentBankDocument->getPrimaryUnit();
1782         int nb = attributes.count();
1783         for (int i = 0; i < nb; ++i) {
1784             // Add actions
1785             QString att = attributes[i];
1786             QString attDisplay = m_currentBankDocument->getDisplay(att);
1787             QString value = selection[0].getAttribute(att);
1788             QString iconName = m_currentBankDocument->getIconName(att);
1789             QString wc = att;
1790             if (att.startsWith(QLatin1String("f_"))) {
1791                 wc += '=' % SKGServices::stringToSqlString(value);
1792             } else {
1793                 wc += "='" % SKGServices::stringToSqlString(value) % '\'';
1794             }
1795             if (att.startsWith(QStringLiteral("f_"))) {
1796                 auto unit = unitp;
1797                 if (!att.contains(QStringLiteral("QUANTITY")) && !att.contains(QStringLiteral("f_BALANCE_ENTERED"))) {
1798                     value = SKGServices::toCurrencyString(SKGServices::stringToDouble(value), unit.Symbol, unit.NbDecimal);
1799                 } else {
1800                     unit.NbDecimal = SKGServices::stringToInt(selection[0].getAttribute(QStringLiteral("i_NBDEC")));
1801                     if (unit.NbDecimal == 0) {
1802                         unit.NbDecimal = 2;
1803                     }
1804                     if (att != QStringLiteral("f_QUANTITYOWNED")) {
1805                         unit.Symbol = selection[0].getAttribute(QStringLiteral("t_UNIT"));
1806                     }
1807                     value = SKGServices::toCurrencyString(SKGServices::stringToDouble(value), unit.Symbol, unit.NbDecimal);
1808                 }
1809             }
1810             QString title = i18nc("A condition", "%1 = %2", attDisplay, value);
1811 
1812             if (!value.isEmpty() && att != attDisplay) {
1813                 QAction* act = m->addAction(m_currentBankDocument->getIcon(att), title);
1814                 if (act != nullptr) {
1815                     connect(act, &QAction::triggered, this, [ title, wc, iconName, modeOperation ]() {
1816                         QString view = modeOperation ? QStringLiteral("v_operation_display") : QStringLiteral("v_suboperation_consolidated");
1817                         // Open
1818                         QDomDocument doc(QStringLiteral("SKGML"));
1819                         doc.setContent(SKGMainPanel::getMainPanel()->getDocument()->getParameter(view != QStringLiteral("v_suboperation_consolidated") ? QStringLiteral("SKGOPERATION_DEFAULT_PARAMETERS") : QStringLiteral("SKGOPERATION_CONSOLIDATED_DEFAULT_PARAMETERS")));
1820                         QDomElement root = doc.documentElement();
1821                         if (root.isNull()) {
1822                             root = doc.createElement(QStringLiteral("parameters"));
1823                             doc.appendChild(root);
1824                         }
1825 
1826 
1827                         root.setAttribute(QStringLiteral("operationTable"), view);
1828                         root.setAttribute(QStringLiteral("operationWhereClause"), wc);
1829                         root.setAttribute(QStringLiteral("title"), title);
1830                         root.setAttribute(QStringLiteral("title_icon"), iconName);
1831 
1832                         SKGMainPanel::getMainPanel()->openPage(SKGMainPanel::getMainPanel()->getPluginByName(QStringLiteral("Skrooge operation plugin")), -1, doc.toString(), modeOperation ? QString() : i18nc("Noun, a list of items", "Sub transactions"));
1833                     });
1834                 }
1835             }
1836         }
1837     }
1838 }
1839 
1840 #include <skgoperationplugin.moc>