/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
 * This file is Skrooge plugin for operation management.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgoperationplugin.h"

#include <KActionCollection>
#include <KStandardAction>
#include <KAboutData>
#include <KGenericFactory>
#include <KStandardDirs>
#include <KToolBarPopupAction>

#include <QDomDocument>

#include "skgoperationpluginwidget.h"
#include "skgoperationboardwidget.h"
#include "skgtraces.h"
#include "skgoperationobject.h"
#include "skgaccountobject.h"
#include "skgtrackerobject.h"
#include "skgbudgetobject.h"
#include "skgsuboperationobject.h"
#include "skgrecurrentoperationobject.h"
#include "skgcategoryobject.h"
#include "skgpayeeobject.h"
#include "skgtransactionmng.h"
#include "skgmainpanel.h"
#include "skgtableview.h"
#include "skghtmlboardwidget.h"
#include "skgruleobject.h"
#include "skgoperation_settings.h"
#include "skgdocumentbank.h"

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGOperationPluginFactory, registerPlugin<SKGOperationPlugin>();)
/**
 * This plugin export.
 */
K_EXPORT_PLUGIN(SKGOperationPluginFactory("skrooge_operation", "skrooge_operation"))

SKGOperationPlugin::SKGOperationPlugin(QWidget* iWidget, QObject* iParent, const QVariantList& /*iArg*/) :
    SKGInterfacePlugin(iParent),
    m_duplicateAction(NULL), m_createTemplateAction(NULL), m_switchToPointedAction(NULL), m_openHighLights(NULL), m_openLastModified(NULL),
    m_openSuboperations(NULL), m_openDuplicate(NULL), m_groupOperation(NULL), m_ungroupOperation(NULL), m_mergeOperationAction(NULL),
    m_open(NULL), m_applyTemplateAction(NULL), m_applyTemplateMenu(NULL), m_currentBankDocument(NULL)
{
    Q_UNUSED(iWidget);
    SKGTRACEINFUNC(10);
}

SKGOperationPlugin::~SKGOperationPlugin()
{
    SKGTRACEINFUNC(10);
    m_currentBankDocument = NULL;
    m_duplicateAction = NULL;
    m_switchToPointedAction = NULL;
    m_openHighLights = NULL;
    m_openLastModified = NULL;
    m_groupOperation = NULL;
    m_ungroupOperation = NULL;
    m_createTemplateAction = NULL;
    m_openSuboperations = NULL;
    m_openDuplicate = NULL;
    m_mergeOperationAction = NULL;
    m_open = NULL;
    m_applyTemplateAction = NULL;
    m_applyTemplateMenu = NULL;
}

bool SKGOperationPlugin::setupActions(SKGDocument* iDocument, const QStringList& iArgument)
{
    SKGTRACEINFUNC(10);
    Q_UNUSED(iArgument);
    m_currentBankDocument = qobject_cast<SKGDocumentBank*>(iDocument);
    if (m_currentBankDocument == NULL) {
        return false;
    }

    m_currentBankDocument->setComputeBalances(skgoperation_settings::computeBalances());

    setComponentData(KGlobal::mainComponent());
    setXMLFile("../skrooge_operation/skrooge_operation.rc");

    // Menu
    // ------------
    m_duplicateAction = new KAction(KIcon("skrooge_duplicate"), i18nc("Verb, duplicate an object",  "Duplicate"), this);
    connect(m_duplicateAction, SIGNAL(triggered(bool)), this, SLOT(onDuplicate()));
    m_duplicateAction->setShortcut(Qt::CTRL + Qt::Key_D);
    registerGlobalAction("edit_duplicate_operation", m_duplicateAction);

    // ------------
    m_createTemplateAction = new KAction(KIcon("skrooge_template"), i18nc("Verb", "Create template"), this);
    connect(m_createTemplateAction, SIGNAL(triggered(bool)), this, SLOT(onCreateTemplate()));
    m_createTemplateAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_T);
    registerGlobalAction("edit_template_operation", m_createTemplateAction);

    // ------------
    m_switchToPointedAction = new KAction(KIcon("dialog-ok"), i18nc("Verb, mark an object", "Point"), this);
    connect(m_switchToPointedAction, SIGNAL(triggered(bool)), this, SLOT(onSwitchToPointed()));
    m_switchToPointedAction->setShortcut(Qt::CTRL + Qt::Key_R);
    registerGlobalAction("edit_point_selected_operation", m_switchToPointedAction);

    // ------------
    KAction* fastEditionAction = new KAction(KIcon("games-solve"), i18nc("Verb", "Fast edit"), this);
    fastEditionAction->setEnabled(false);
    fastEditionAction->setShortcut(Qt::Key_F10);
    registerGlobalAction("fast_edition", fastEditionAction);

    // ------------
    QStringList overlayopen;
    overlayopen.push_back("skg_open");

    QStringList overlayrun;
    overlayrun.push_back("system-run");
    m_open = new KAction(KIcon(icon(), NULL, overlayopen), i18nc("Verb", "Open operations..."), this);
    connect(m_open, SIGNAL(triggered(bool)), this, SLOT(onOpenOperations()));
    registerGlobalAction("open", m_open);

    m_openHighLights = new KAction(KIcon("bookmarks", NULL, overlayopen), i18nc("Verb", "Open highlights..."), this);
    m_openHighLights->setData(QString("skg://skrooge_operation_plugin/?title_icon=bookmarks&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Highlighted operations")) %
                                      "&operationWhereClause=" % SKGServices::encodeForUrl("t_bookmarked='Y'")));
    connect(m_openHighLights, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    m_openHighLights->setShortcut(Qt::CTRL + Qt::META + Qt::Key_H);
    registerGlobalAction("view_open_highlight", m_openHighLights);

    // ------------
    m_openLastModified = new KAction(KIcon("view-refresh", NULL, overlayopen), i18nc("Verb", "Open last modified..."), this);
    m_openLastModified->setData(QString("skg://skrooge_operation_plugin/?title_icon=view-refresh&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations modified or created during last action")) %
                                        "&operationWhereClause=" % SKGServices::encodeForUrl("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))")));
    connect(m_openLastModified, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    m_openLastModified->setShortcut(Qt::META + Qt::Key_L);
    registerGlobalAction("view_open_last_modified", m_openLastModified);

    // ------------
    m_openSuboperations = new KAction(KIcon("split", NULL, overlayopen), i18nc("Verb", "Open sub operations..."), this);
    m_openSuboperations->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 operations"))));
    connect(m_openSuboperations, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    m_openSuboperations->setShortcut(Qt::META + Qt::Key_S);
    registerGlobalAction("view_open_suboperations", m_openSuboperations);

    // ------------
    m_openDuplicate = new KAction(KIcon("skrooge_duplicate", NULL, overlayopen), i18nc("Verb", "Open potential duplicates..."), this);
    m_openDuplicate->setData(QString("skg://skrooge_operation_plugin/?title_icon=skrooge_duplicate&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations potentially duplicated")) %
                                     "&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')))")));
    connect(m_openDuplicate, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    m_openDuplicate->setShortcut(Qt::META + Qt::Key_D);
    registerGlobalAction("view_open_duplicates", m_openDuplicate);

    // ------------
    KAction* actTmp = new KAction(KIcon("view-financial-transfer", NULL, overlayopen), i18nc("Verb", "Open transfers without payee..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=skrooge_payee&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transfers without payee")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("t_TRANSFER='Y' AND r_payee_id=0")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_transfers_without_payee", actTmp);

    // ------------
    actTmp = new KAction(KIcon("skrooge_payee", NULL, overlayopen), i18nc("Verb", "Open operations without payee..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=skrooge_payee&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations without payee")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("t_TRANSFER='N' AND r_payee_id=0")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_operation_without_payee", actTmp);

    // ------------
    actTmp = new KAction(KIcon("view-financial-transfer", NULL, overlayopen), i18nc("Verb", "Open transfers without category..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=skrooge_category&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Transfers without category")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("t_TRANSFER='Y' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_transfers_without_category", actTmp);

    // ------------
    actTmp = new KAction(KIcon("skrooge_category", NULL, overlayopen), i18nc("Verb", "Open operations without category..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=skrooge_category&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations without category")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("t_TRANSFER='N' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_operation_without_category", actTmp);

    // ------------
    actTmp = new KAction(KIcon("skrooge_credit_card", NULL, overlayopen), i18nc("Verb", "Open operations without mode..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=skrooge_credit_card&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations without mode")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("t_template='N' AND t_mode='' AND d_date<>'0000-00-00'")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_operation_without_mode", actTmp);

    // ------------
    actTmp = new KAction(KIcon("draw-freehand", NULL, overlayopen), i18nc("Verb", "Open operations with comments not aligned..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=draw-freehand&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations with comments not aligned")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("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)")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_operation_with_comment_not_aligned", actTmp);

    // ------------
    actTmp = new KAction(KIcon("view-pim-calendar", NULL, overlayopen), i18nc("Verb", "Open operations with dates not aligned..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=draw-freehand&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations with dates not aligned")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("id IN (SELECT op.id FROM operation op, suboperation so WHERE so.rd_operation_id=op.id AND so.d_date<>op.d_date AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1)")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_operation_with_date_not_aligned", actTmp);

    // ------------
    actTmp = new KAction(KIcon("draw-freehand", NULL, overlayrun), i18nc("Verb", "Align comment of suboperations"), this);
    connect(actTmp, SIGNAL(triggered(bool)), this, SLOT(onAlignComment()));
    registerGlobalAction("clean_align_comment", actTmp);

    // ------------
    actTmp = new KAction(KIcon("view-pim-calendar", NULL, overlayrun), i18nc("Verb", "Align date of suboperations"), this);
    connect(actTmp, SIGNAL(triggered(bool)), this, SLOT(onAlignDate()));
    registerGlobalAction("clean_align_date", actTmp);

    // ------------
    actTmp = new KAction(KIcon("view-financial-transfer", NULL, overlayopen), i18nc("Verb", "Open operations in groups with only one operation..."), this);
    actTmp->setData(QString("skg://skrooge_operation_plugin/?title_icon=view-financial-transfer&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations in groups with only one operation")) %
                            "&operationWhereClause=" % SKGServices::encodeForUrl("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")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_operation_in_group_of_one", actTmp);

    // ------------
    actTmp = new KAction(KIcon("view-financial-transfer", NULL, overlayrun), i18nc("Verb", "Remove groups with only one operation."), this);
    connect(actTmp, SIGNAL(triggered(bool)), this, SLOT(onRemoveGroupWithOneOperation()));
    registerGlobalAction("clean_remove_group_with_one_operation", actTmp);

    // ------------
    m_groupOperation = new KAction(KIcon("view-financial-transfer"), i18nc("Verb", "Group operations"), this);
    connect(m_groupOperation, SIGNAL(triggered(bool)), this, SLOT(onGroupOperation()));
    m_groupOperation->setShortcut(Qt::CTRL + Qt::Key_G);
    registerGlobalAction("edit_group_operation", m_groupOperation);

    // ------------
    QStringList overlay;
    overlay.push_back("edit-delete");
    m_ungroupOperation = new KAction(KIcon("view-financial-transfer", NULL, overlay), i18nc("Verb", "Ungroup operations"), this);
    connect(m_ungroupOperation, SIGNAL(triggered(bool)), this, SLOT(onUngroupOperation()));
    m_ungroupOperation->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_G);
    registerGlobalAction("edit_ungroup_operation", m_ungroupOperation);

    // ------------
    m_mergeOperationAction = new KAction(KIcon("split"), i18nc("Verb, action to merge", "Merge sub operations"), this);
    connect(m_mergeOperationAction, SIGNAL(triggered(bool)), SLOT(onMergeSubOperations()));
    m_mergeOperationAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_M);
    registerGlobalAction("merge_sub_operations", m_mergeOperationAction);

    m_applyTemplateAction = new KToolBarPopupAction(KIcon("edit-apply-template"), i18nc("Verb, action to apply a template", "Apply template"), this);
    m_applyTemplateMenu = static_cast<KMenu*>(m_applyTemplateAction->menu());
    connect(m_applyTemplateMenu , SIGNAL(aboutToShow()), this, SLOT(onShowApplyTemplateMenu()));
    m_applyTemplateAction->setStickyMenu(false);
    m_applyTemplateAction->setData(1);
    registerGlobalAction("edit_apply_template", m_applyTemplateAction);


    return true;
}

void SKGOperationPlugin::onShowApplyTemplateMenu()
{
    if (m_applyTemplateMenu && m_currentBankDocument) {
        // Clean Menu
        QMenu* m = m_applyTemplateMenu;
        m->clear();

        // Search templates
        SKGStringListList listTmp;
        m_currentBankDocument->executeSelectSqliteOrder(
            "SELECT t_displayname, id, t_bookmarked FROM v_operation_displayname WHERE t_template='Y' ORDER BY t_bookmarked DESC, t_PAYEE ASC",
            listTmp);

        // Build menus
        int count = 0;
        bool fav = true;
        int nb = listTmp.count();
        for (int i = 1; i < nb; ++i) {
            // Add more sub menu
            if (count == 8) {
                m = m->addMenu(i18nc("More items in a menu", "More"));
                count = 0;
            }
            count++;

            // Add separator for bookmarked templates
            if (fav && listTmp.at(i).at(2) == "N" && i > 1) {
                m->addSeparator();
            }
            fav = (listTmp.at(i).at(2) == "Y");

            // Add actions
            QAction* act = m->addAction(KIcon("skrooge_template"), listTmp.at(i).at(0));
            if (act) {
                act->setData(listTmp.at(i).at(1));
                connect(act, SIGNAL(triggered()), this, SLOT(onApplyTemplate()));
            }
        }
    }
}

void SKGOperationPlugin::refresh()
{
    SKGTRACEINFUNC(10);
    if (m_currentBankDocument && SKGMainPanel::getMainPanel()) {
        bool test = (m_currentBankDocument->getDatabase() != NULL);
        if (m_openHighLights) {
            m_openHighLights->setEnabled(test);
        }
        if (m_openSuboperations) {
            m_openSuboperations->setEnabled(test);
        }
        if (m_openDuplicate) {
            m_openDuplicate->setEnabled(test);
        }
        if (m_openLastModified) {
            m_openLastModified->setEnabled(test && m_currentBankDocument->getNbTransaction() > 0);
        }

        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        if (m_open) {
            m_open->setEnabled(nb > 0);
        }
        if (nb > 0) {
            bool onOperation = (selection.at(0).getRealTable() == "operation");
            if (m_duplicateAction) {
                m_duplicateAction->setEnabled(onOperation);
            }
            if (m_createTemplateAction) {
                m_createTemplateAction->setEnabled(onOperation);
            }
            if (m_switchToPointedAction) {
                m_switchToPointedAction->setEnabled(onOperation);
            }
            if (m_groupOperation) {
                m_groupOperation->setEnabled(onOperation && selection.count() >= 2);
            }
            if (m_ungroupOperation) {
                m_ungroupOperation->setEnabled(onOperation);
            }
            if (m_mergeOperationAction) {
                m_mergeOperationAction->setEnabled(onOperation);
            }
            if (m_applyTemplateAction) {
                m_applyTemplateAction->setEnabled(onOperation);
            }
        } else {
            if (m_duplicateAction) {
                m_duplicateAction->setEnabled(false);
            }
            if (m_createTemplateAction) {
                m_createTemplateAction->setEnabled(false);
            }
            if (m_switchToPointedAction) {
                m_switchToPointedAction->setEnabled(false);
            }
            if (m_groupOperation) {
                m_groupOperation->setEnabled(false);
            }
            if (m_ungroupOperation) {
                m_ungroupOperation->setEnabled(false);
            }
            if (m_mergeOperationAction) {
                m_mergeOperationAction->setEnabled(false);
            }
            if (m_applyTemplateAction) {
                m_applyTemplateAction->setEnabled(false);
            }
        }
    }
}

int SKGOperationPlugin::getNbDashboardWidgets()
{
    return 2;
}

QString SKGOperationPlugin::getDashboardWidgetTitle(int iIndex)
{
    if (iIndex == 0) {
        return i18nc("Noun, the title of a section", "Income && Expenditure");
    }
    return i18nc("Noun, the title of a section", "Highlighted operations");
}

SKGBoardWidget* SKGOperationPlugin::getDashboardWidget(int iIndex)
{
    if (iIndex == 0) {
        return new SKGOperationBoardWidget(m_currentBankDocument);
    }
    return new SKGHtmlBoardWidget(m_currentBankDocument,
                                  getDashboardWidgetTitle(iIndex),
                                  KStandardDirs().findResource("data", "skrooge/html/default/highlighted_operations.html"),
                                  QStringList() << "operation");
}

SKGTabPage* SKGOperationPlugin::getWidget()
{
    SKGTRACEINFUNC(10);
    return new SKGOperationPluginWidget(m_currentBankDocument);
}

QWidget* SKGOperationPlugin::getPreferenceWidget()
{
    SKGTRACEINFUNC(10);
    QWidget* w = new QWidget();
    ui.setupUi(w);

    // Set labels
    ui.kPayeeFakeLbl->setText(i18n("%1:", m_currentBankDocument->getDisplay("t_payee")));
    ui.kCategoryFakeLbl->setText(i18n("%1:", m_currentBankDocument->getDisplay("t_CATEGORY")));
    ui.kCommentFakeLbl->setText(i18n("%1:", m_currentBankDocument->getDisplay("t_comment")));

    ui.kCategoryCommissionLbl->setText(ui.kCategoryFakeLbl->text());
    ui.kCommentCommissionLbl->setText(ui.kCommentFakeLbl->text());

    ui.kCategoryTaxLbl->setText(ui.kCategoryFakeLbl->text());
    ui.kCommentTaxLbl->setText(ui.kCommentFakeLbl->text());

    // Fill combo boxes and auto complession
    SKGMainPanel::fillWithDistinctValue(ui.kcfg_categoryFakeOperation, m_currentBankDocument, "category", "t_fullname", "");
    SKGMainPanel::fillWithDistinctValue(ui.kcfg_payeeFakeOperation, m_currentBankDocument, "payee", "t_name", "");
    SKGMainPanel::fillWithDistinctValue(ui.kcfg_commentFakeOperation, m_currentBankDocument, "v_operation_all_comment", "t_comment", "", true);

    SKGMainPanel::fillWithDistinctValue(ui.kcfg_categoryCommissionOperation, m_currentBankDocument, "category", "t_fullname", "");
    SKGMainPanel::fillWithDistinctValue(ui.kcfg_commentCommissionOperation, m_currentBankDocument, "v_operation_all_comment", "t_comment", "", true);

    SKGMainPanel::fillWithDistinctValue(ui.kcfg_categoryTaxOperation, m_currentBankDocument, "category", "t_fullname", "");
    SKGMainPanel::fillWithDistinctValue(ui.kcfg_commentTaxOperation, m_currentBankDocument, "v_operation_all_comment", "t_comment", "", true);

    return w;
}

KConfigSkeleton* SKGOperationPlugin::getPreferenceSkeleton()
{
    return skgoperation_settings::self();
}

SKGError SKGOperationPlugin::savePreferences() const
{
    m_currentBankDocument->setComputeBalances(skgoperation_settings::computeBalances());
    return SKGInterfacePlugin::savePreferences();
}

QString SKGOperationPlugin::title() const
{
    return i18nc("Noun", "Operations");
}

QString SKGOperationPlugin::icon() const
{
    return "view-financial-list";
}

QString SKGOperationPlugin::toolTip() const
{
    return i18nc("Noun", "Operation management");
}

int SKGOperationPlugin::getOrder() const
{
    return 15;
}

QStringList SKGOperationPlugin::tips() const
{
    QStringList output;
    output.push_back(i18nc("Description of a tips", "<p>... you can press <strong>+</strong>, <strong>-</strong>, <strong>CTRL +</strong> or <strong>CTRL -</strong> to quickly change dates.</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... you can update many operations in one shot.</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... you can double click on an operation to show or edit sub operations.</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... you can duplicate an operation including complex operations (split, grouped, ...).</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... you can create template of operations.</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... you can group and ungroup operations.</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... you have to indicate the sign of an operation only if you want to force it, else it will be determined automatically with the category.</p>"));
    return output;
}

bool SKGOperationPlugin::isInPagesChooser() const
{
    return true;
}

void SKGOperationPlugin::onApplyTemplate()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    QAction* act = qobject_cast<QAction*>(sender());
    if (act) {
        // Get template
        SKGOperationObject temp(m_currentBankDocument, SKGServices::stringToInt(act->data().toString()));

        // Get Selection
        if (SKGMainPanel::getMainPanel() && m_currentBankDocument) {
            QStringList listUUID;

            SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
            int nb = selection.count();
            {
                SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Apply template"), err, nb);
                for (int i = 0; !err && i < nb; ++i) {
                    SKGOperationObject operationObj(selection.at(i));

                    SKGOperationObject op;
                    IFOKDO(err, temp.duplicate(op))
                    IFOKDO(err, op.mergeAttribute(operationObj, SKGOperationObject::PROPORTIONAL, false))
                    listUUID.push_back(op.getUniqueID());

                    IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
                }
            }

            // status bar
            IFOK(err) {
                err = SKGError(0, i18nc("Successful message after an user action", "Template applied."));
                SKGOperationPluginWidget* w = qobject_cast<SKGOperationPluginWidget*>(SKGMainPanel::getMainPanel()->currentPage());
                if (w) {
                    w->getTableView()->selectObjects(listUUID, true);
                }
            } else {
                err.addError(ERR_FAIL, i18nc("Error message",  "Apply of template failed"));
            }
        }
        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGOperationPlugin::onGroupOperation()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    // Get Selection
    if (SKGMainPanel::getMainPanel() && m_currentBankDocument) {
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        if (nb >= 2) {
            SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Group operations"), err, nb);
            SKGOperationObject main(selection.at(0));
            IFOKDO(err, m_currentBankDocument->stepForward(1))
            for (int i = 1; !err && i < nb; ++i) {
                SKGOperationObject operationObj(selection.at(i));
                IFOKDO(err, operationObj.setGroupOperation(main))
                IFOKDO(err, operationObj.save())
                IFOKDO(err, main.load())

                // Send message
                IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The operation '%1' has been grouped with '%2'", operationObj.getDisplayName(), main.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
            }
        }

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Operations grouped.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Group creation failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGOperationPlugin::onUngroupOperation()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    // Get Selection
    if (SKGMainPanel::getMainPanel() && m_currentBankDocument) {
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        {
            SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Ungroup operation"), err, nb);
            for (int i = 0; !err && i < nb; ++i) {
                SKGOperationObject operationObj(selection.at(i));
                IFOKDO(err, operationObj.setGroupOperation(operationObj))
                IFOKDO(err, operationObj.save())

                // Send message
                IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The operation '%1' has been ungrouped", operationObj.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
            }
        }

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Operation ungrouped.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Group deletion failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGOperationPlugin::onSwitchToPointed()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    // Get Selection
    if (SKGMainPanel::getMainPanel() && m_currentBankDocument) {
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        {
            SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Switch to pointed"), err, nb);
            for (int i = 0; !err && i < nb; ++i) {
                SKGOperationObject operationObj(selection.at(i));
                IFOKDO(err, operationObj.setStatus(operationObj.getStatus() != SKGOperationObject::POINTED ? SKGOperationObject::POINTED : SKGOperationObject::NONE))
                IFOKDO(err, operationObj.save())

                // Send message
                IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The status of the operation '%1' has been changed", operationObj.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
            }
        }

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Operation pointed.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Switch failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGOperationPlugin::onDuplicate()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    // Get Selection
    if (SKGMainPanel::getMainPanel() && m_currentBankDocument) {
        QStringList listUUID;
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        {
            SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Duplicate operation"), err, nb);
            for (int i = 0; !err && i < nb; ++i) {
                SKGOperationObject operationObj(selection.at(i));
                SKGOperationObject dup;
                IFOKDO(err, operationObj.duplicate(dup))
                IFOKDO(err, m_currentBankDocument->stepForward(i + 1))

                // Send message
                IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The duplicate operation '%1' has been added", dup.getDisplayName()), SKGDocument::Hidden));


                listUUID.push_back(dup.getUniqueID());
            }
        }

        // status bar
        IFOK(err) {
            err = SKGError(0, i18nc("Successful message after an user action", "Operation duplicated."));
            SKGOperationPluginWidget* w = qobject_cast<SKGOperationPluginWidget*>(SKGMainPanel::getMainPanel()->currentPage());
            if (w) {
                w->getTableView()->selectObjects(listUUID, true);
            }
        } else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Duplicate operation failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGOperationPlugin::onCreateTemplate()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    // Get Selection
    if (SKGMainPanel::getMainPanel() && m_currentBankDocument) {
        QStringList listUUID;
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        {
            SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Create template"), err, nb);
            for (int i = 0; !err && i < nb; ++i) {
                SKGOperationObject operationObj(selection.at(i));
                SKGOperationObject dup;
                IFOKDO(err, operationObj.duplicate(dup , QDate::currentDate(), true))
                IFOKDO(err, m_currentBankDocument->stepForward(i + 1))

                // Send message
                IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The template '%1' has been added", dup.getDisplayName()), SKGDocument::Hidden));

                listUUID.push_back(dup.getUniqueID());
            }
        }

        // status bar
        IFOK(err) {
            err = SKGError(0, i18nc("Successful message after an user action", "Template created."));
            SKGOperationPluginWidget* w = qobject_cast<SKGOperationPluginWidget*>(SKGMainPanel::getMainPanel()->currentPage());
            if (w) {
                w->setTemplateMode(true);
                w->getTableView()->selectObjects(listUUID, true);
            }
        } else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Creation template failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGOperationPlugin::onOpenOperations()
{
    SKGTRACEINFUNC(10);
    if (SKGMainPanel::getMainPanel()) {
        SKGObjectBase::SKGListSKGObjectBase selection;
        QAction* act = qobject_cast< QAction* >(sender());
        if (act) {
            QStringList data = SKGServices::splitCSVLine(act->data().toString(), '-');
            if (data.count() == 2) {
                selection.push_back(SKGObjectBase(m_currentBankDocument, data[1], SKGServices::stringToInt(data[0])));
            }
        }
        if (selection.isEmpty()) {
            selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        }

        int nb = selection.count();
        if (nb > 0) {
            QString wc;
            QString titleOpen;
            QString iconOpen;
            QString table = selection.at(0).getRealTable();
            QString view;
            QString account;
            if (table == "account") {
                if (nb == 1) {
                    SKGAccountObject tmp(selection.at(0));
                    account = tmp.getName();
                } else {
                    // Build whereclause and title
                    wc = "rd_account_id in (";
                    titleOpen = i18nc("Noun, a list of items", "Operations of account:");

                    for (int i = 0; i < nb; ++i) {
                        SKGAccountObject tmp(selection.at(i));
                        if (i) {
                            wc += ',';
                            titleOpen += ',';
                        }
                        wc += SKGServices::intToString(tmp.getID());
                        titleOpen += i18n("'%1'", tmp.getDisplayName());
                    }
                    wc += ')';
                    // Set icon
                    iconOpen = "view-bank-account";
                }
            } else if (table == "unit") {
                // Build whereclause and title
                wc = "rc_unit_id in (";
                titleOpen = i18nc("Noun, a list of items", "Operations with unit:");

                for (int i = 0; i < nb; ++i) {
                    SKGUnitObject tmp(selection.at(i));
                    if (i) {
                        wc += ',';
                        titleOpen += ',';
                    }
                    wc += SKGServices::intToString(tmp.getID());
                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }
                wc += ')';

                // Set icon
                iconOpen = "view-currency-list";
            } else if (table == "category") {
                titleOpen = i18nc("Noun, a list of items", "Sub operations with category:");

                for (int i = 0; i < nb; ++i) {
                    SKGCategoryObject tmp(selection.at(i));
                    if (i) {
                        wc += " OR ";
                        titleOpen += ',';
                    }

                    QString name = tmp.getFullName();

                    wc += "(t_REALCATEGORY";
                    if (name.isEmpty()) {
                        wc += " IS NULL OR t_REALCATEGORY='')";
                    } else {
                        wc += " = '" % SKGServices::stringToSqlString(name) % "' OR t_REALCATEGORY like '" % SKGServices::stringToSqlString(name) % OBJECTSEPARATOR % "%')";
                    }

                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }

                // Set icon
                iconOpen = "skrooge_category";
                view = "v_suboperation_consolidated";
            } else if (table == "refund") {
                // Build whereclause and title
                wc = "t_REALREFUND in (";
                titleOpen = i18nc("Noun, a list of items", "Sub operations followed by tracker:");

                for (int i = 0; i < nb; ++i) {
                    SKGTrackerObject tmp(selection.at(i));
                    if (i) {
                        wc += ',';
                        titleOpen += ',';
                    }
                    wc += '\'' % SKGServices::stringToSqlString(tmp.getName()) % '\'';
                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }
                wc += ')';

                // Set icon
                iconOpen = "skrooge_tracker";
                view = "v_suboperation_consolidated";
            } else if (table == "payee") {
                // Build whereclause and title
                wc = "r_payee_id in (";
                titleOpen = i18nc("Noun, a list of items", "Operations assigned to payee:");

                for (int i = 0; i < nb; ++i) {
                    SKGPayeeObject tmp(selection.at(i));
                    if (i) {
                        wc += ',';
                        titleOpen += ',';
                    }
                    wc += SKGServices::intToString(tmp.getID());
                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }
                wc += ')';

                // Set icon
                iconOpen = "skrooge_payee";
            } else if (table == "budget") {
                titleOpen = i18nc("Noun, a list of items", "Operations assigned to budget:");

                for (int i = 0; i < nb; ++i) {
                    SKGBudgetObject tmp(selection.at(i));
                    if (i) {
                        wc += " OR ";
                        titleOpen += ',';
                    }

                    wc += "(t_TYPEACCOUNT<>'L' AND i_SUBOPID IN (SELECT b2.id_suboperation FROM budgetsuboperation b2 WHERE b2.id=" % SKGServices::intToString(tmp.getID()) % "))";
                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }

                // Set icon
                iconOpen = "view-income-categories";
                view = "v_suboperation_consolidated";
            } else if (table == "recurrentoperation") {
                titleOpen = i18nc("Noun, a list of items", "Scheduled operations:");

                for (int i = 0; i < nb; ++i) {
                    SKGRecurrentOperationObject tmp(selection.at(i));
                    if (i) {
                        wc += " OR ";
                        titleOpen += ',';
                    }

                    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()) + ')';
                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }

                // Set icon
                iconOpen = "chronometer";
                view = "v_operation_display";
            } else if (table == "rule") {
                titleOpen = i18nc("Noun, a list of items", "Sub operations corresponding to rule:");

                for (int i = 0; i < nb; ++i) {
                    SKGRuleObject tmp(selection.at(i));
                    if (i) {
                        wc += " OR ";
                        titleOpen += ',';
                    }

                    wc += "i_SUBOPID in (SELECT i_SUBOPID FROM v_operation_prop WHERE " % tmp.getSelectSqlOrder() % ')';
                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }

                // Set icon
                iconOpen = "edit-find";
                view = "v_suboperation_consolidated";
            } else if (table == "operation") {
                // Build whereclause and title
                titleOpen = i18nc("Noun, a list of items", "Sub operations grouped or split of:");
                view = "v_suboperation_consolidated";

                for (int i = 0; i < nb; ++i) {
                    SKGOperationObject tmp(selection.at(i));
                    if (i) {
                        wc += " OR ";
                        titleOpen += ',';
                    }

                    int opid = tmp.getID();
                    wc += QString("(i_OPID=") % SKGServices::intToString(opid);

                    opid = SKGServices::stringToInt(tmp.getAttribute("i_group_id"));
                    if (opid != 0) {
                        wc += " or i_group_id=" % SKGServices::intToString(opid);
                    }
                    wc += ')';

                    titleOpen += i18n("'%1'", tmp.getDisplayName());
                }

                // Set icon
                iconOpen = "view-financial-list";
            } else if (table == "suboperation") {
                // Build whereclause and title
                titleOpen = i18nc("Noun, a list of items", "Operations grouped with:");
                view = "v_operation_display_all";

                for (int i = 0; i < nb; ++i) {
                    SKGSubOperationObject tmp(selection.at(i).getDocument(), selection.at(i).getID());
                    SKGOperationObject op;
                    tmp.getParentOperation(op);
                    if (i) {
                        wc += ',';
                        titleOpen += ',';
                    }

                    int opid = op.getID();
                    wc += QString("(id=") % SKGServices::intToString(opid);

                    opid = SKGServices::stringToInt(op.getAttribute("i_group_id"));
                    if (opid != 0) {
                        wc += " or i_group_id=" % SKGServices::intToString(opid);
                    }
                    wc += ')';

                    titleOpen += i18n("'%1'", op.getDisplayName());
                }

                // Set icon
                iconOpen = "view-financial-list";
            }

            // Open
            QDomDocument doc("SKGML");
            doc.setContent(SKGMainPanel::getMainPanel()->getDocument()->getParameter(view != "v_suboperation_consolidated" ? "SKGOPERATION_DEFAULT_PARAMETERS" : "SKGOPERATION_CONSOLIDATED_DEFAULT_PARAMETERS"));
            QDomElement root = doc.documentElement();
            if (root.isNull()) {
                root = doc.createElement("parameters");
                doc.appendChild(root);
            }

            if (!view.isEmpty()) {
                root.setAttribute("operationTable", view);
            }
            if (!wc.isEmpty()) {
                root.setAttribute("operationWhereClause", wc);
            }
            if (!titleOpen.isEmpty()) {
                root.setAttribute("title", titleOpen);
            }
            if (!iconOpen.isEmpty()) {
                root.setAttribute("title_icon", iconOpen);
            }
            if (!account.isEmpty()) {
                root.setAttribute("account", account);
            } else {
                root.setAttribute("currentPage", "-1");
            }

            SKGMainPanel::getMainPanel()->openPage(SKGMainPanel::getMainPanel()->getPluginByName("Skrooge operation plugin"), -1, doc.toString());
        }
    }
}

SKGAdviceList SKGOperationPlugin::advice(const QStringList& iIgnoredAdvice)
{
    SKGTRACEINFUNC(10);
    SKGAdviceList output;

    // Search duplicate number on operation
    if (!iIgnoredAdvice.contains("skgoperationplugin_duplicate")) {
        SKGStringListList result;
        m_currentBankDocument->executeSelectSqliteOrder("SELECT count(1), t_ACCOUNT, i_number FROM v_operation_display WHERE i_number!=0 GROUP BY t_ACCOUNT, i_number HAVING count(1)>1 ORDER BY count(1) DESC", result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QStringList line = result.at(i);
            QString account = line.at(1);
            QString number = line.at(2);

            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_duplicate|" % number % ';' % account);
            ad.setPriority(7);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Duplicate number %1 in account '%2'", number, account));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Your account '%1' contains more than one operation with number %2.The operation number should be unique (check number, transaction reference...)", account, number));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = i18nc("Advice on making the best (action)", "Edit operations with duplicate number");
                a.IconName = "skg_open";
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check operations not reconciliated
    if (!iIgnoredAdvice.contains("skgoperationplugin_notreconciliated")) {
        SKGStringListList result;
        m_currentBankDocument->executeSelectSqliteOrder("SELECT count(1), t_ACCOUNT FROM v_operation_display WHERE t_status='N' GROUP BY t_ACCOUNT HAVING count(1)>100 ORDER BY count(1) DESC", result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QStringList line = result.at(i);
            QString account = line.at(1);

            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_notreconciliated|" % account);
            ad.setPriority(9);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Many operations of '%1' not reconciliated", account));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Don't forget to reconciliate your accounts. By doing so, you acknowledge that your bank has indeed processed these operations on your account. This is how you enforce compliance with your bank's statements. See online help for more details"));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = i18nc("Advice on making the best (action)", "Open account '%1' for reconciliation", account);
                a.IconName = "skg_open";
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check operations without mode
    if (!iIgnoredAdvice.contains("skgimportexportplugin_notvalidated")) {
        bool exist = false;
        m_currentBankDocument->existObjects("operation", "t_template='N' AND t_mode='' AND d_date<>'0000-00-00'", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgimportexportplugin_notvalidated");
            ad.setPriority(5);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Many operations don't have mode"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Don't forget to set a mode for each operation. This will allow you to generate better reports."));
            QStringList autoCorrections;
            autoCorrections.push_back("skg://view_open_operation_without_mode");
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check operations without category
    if (!iIgnoredAdvice.contains("skgoperationplugin_nocategory")) {
        bool exist = false;
        m_currentBankDocument->existObjects("v_operation_display", "t_TRANSFER='N' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_nocategory");
            ad.setPriority(5);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Many operations don't have category"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Don't forget to associate a category for each operation. This will allow you to generate better reports."));
            QStringList autoCorrections;
            autoCorrections.push_back("skg://view_open_operation_without_category");
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    if (!iIgnoredAdvice.contains("skgoperationplugin_transfer_nocategory")) {
        bool exist = false;
        m_currentBankDocument->existObjects("v_operation_display", "t_TRANSFER='Y' AND EXISTS (SELECT 1 FROM suboperation WHERE rd_operation_id=v_operation_display.id AND r_category_id=0)", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_transfer_nocategory");
            ad.setPriority(3);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transfers don't have category"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Don't forget to associate a category for each transfer."));
            QStringList autoCorrections;
            autoCorrections.push_back("skg://view_open_transfers_without_category");
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check operations without payee
    if (!iIgnoredAdvice.contains("skgoperationplugin_nopayee")) {
        bool exist = false;
        m_currentBankDocument->existObjects("v_operation_display", "t_TRANSFER='N' AND r_payee_id=0", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_nopayee");
            ad.setPriority(5);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Many operations don't have payee"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Don't forget to associate a payee for each operation. This will allow you to generate better reports."));
            QStringList autoCorrections;
            autoCorrections.push_back("skg://view_open_operation_without_payee");
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    if (!iIgnoredAdvice.contains("skgoperationplugin_transfer_nopayee")) {
        bool exist = false;
        m_currentBankDocument->existObjects("v_operation_display", "t_TRANSFER='Y' AND r_payee_id=0", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_transfer_nopayee");
            ad.setPriority(3);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Many transfers don't have payee"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Don't forget to associate a payee for each transfer."));
            QStringList autoCorrections;
            autoCorrections.push_back("skg://view_open_transfers_without_payee");
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check operations in group of only one transaction
    if (!iIgnoredAdvice.contains("skgoperationplugin_groupofone")) {
        bool exist = false;
        m_currentBankDocument->existObjects("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", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_groupofone");
            ad.setPriority(4);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Some operations are in groups with only one operation"));
            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 operation. This makes no sense."));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://view_open_operation_in_group_of_one";
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://clean_remove_group_with_one_operation";
                a.IsRecommended = true;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check simple operations with comment not aligned with the comment of the suboperation
    if (!iIgnoredAdvice.contains("skgoperationplugin_commentsnotaligned")) {
        bool exist = false;
        m_currentBankDocument->existObjects("operation op, suboperation so", "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", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_commentsnotaligned");
            ad.setPriority(5);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Some simple operations don't have their comments aligned"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "The comment of the operation is not aligned with the comment of the suboperation."));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://view_open_operation_with_comment_not_aligned";
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://clean_align_comment";
                a.IsRecommended = true;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check simple operations with date not aligned with the date of the suboperation
    if (!iIgnoredAdvice.contains("skgoperationplugin_datesnotaligned")) {
        bool exist = false;
        m_currentBankDocument->existObjects("operation op, suboperation so", "so.rd_operation_id=op.id AND so.d_date<>op.d_date AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_datesnotaligned");
            ad.setPriority(5);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Some simple operations don't have their dates aligned"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "The date of the operation is not aligned with the date of the suboperation."));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://view_open_operation_with_date_not_aligned";
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://clean_align_date";
                a.IsRecommended = true;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Too much money in your account
    if (!iIgnoredAdvice.contains("skgoperationplugin_too_much_money")) {
        SKGStringListList result;
        m_currentBankDocument->executeSelectSqliteOrder("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_maxamount*0.9) ORDER BY f_RATE DESC", result);
        int nb = result.count();
        if (nb > 1) {
            // Get better interest account
            QString target = result.at(1).at(0);
            QString rate = result.at(1).at(1);

            // Get accounts with too much money
            m_currentBankDocument->executeSelectSqliteOrder("SELECT t_name FROM v_account_display WHERE t_close='N' AND ("
                    // Interest are computed on amount over the limit too, not need to transfer them "(t_maxamount_enabled='Y' AND f_CURRENTAMOUNT>f_maxamount) OR "
                    "(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','start of month', '-1 MONTH')))))"
                    ")", result);
            nb = result.count();
            for (int i = 1; i < nb; ++i) {  // Ignore header
                // Get parameters
                QString account = result.at(i).at(0);

                SKGAdvice ad;
                ad.setUUID("skgoperationplugin_too_much_money|" % account);
                ad.setPriority(6);
                ad.setShortMessage(i18nc("Advice on making the best (short)", "Too much money in your account '%1'", account));
                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));
                output.push_back(ad);
            }
        }
    }

    // Not enough money in your account
    if (!iIgnoredAdvice.contains("skgoperationplugin_minimum_limit")) {
        SKGStringListList result;

        // Get accounts with amount close or below the minimum limit
        m_currentBankDocument->executeSelectSqliteOrder("SELECT t_name FROM v_account_display WHERE t_close='N' AND t_minamount_enabled='Y' AND f_CURRENTAMOUNT<f_minamount", result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QString account = result.at(i).at(0);

            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_minimum_limit|" % account);
            ad.setPriority(9);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Not enough money in your account '%1'", account));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "The amount of this account is below the minimum limit. You should replenish it."));
            output.push_back(ad);
        }
    }

    // Not enough money in your account
    if (!iIgnoredAdvice.contains("skgoperationplugin_maximum_limit")) {
        SKGStringListList result;

        // Get accounts with amount over the maximum limit
        m_currentBankDocument->executeSelectSqliteOrder("SELECT t_name FROM v_account_display WHERE t_close='N' AND t_maxamount_enabled='Y' AND f_CURRENTAMOUNT>f_maxamount", result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QString account = result.at(i).at(0);

            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_maximum_limit|" % account);
            ad.setPriority(6);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Balance in account '%1' exceeds the maximum limit", account));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "The balance of this account exceeds the maximum limit."));
            output.push_back(ad);
        }
    }

    // Not enough money in your account
    if (!iIgnoredAdvice.contains("skgoperationplugin_close_minimum_limit")) {
        SKGStringListList result;

        // Get accounts with amount close or below the minimum limit
        m_currentBankDocument->executeSelectSqliteOrder("SELECT t_name FROM v_account_display WHERE t_close='N' AND t_minamount_enabled='Y' AND t_maxamount_enabled='Y'"
                " AND f_CURRENTAMOUNT>=f_minamount AND f_CURRENTAMOUNT<=f_minamount+0.1*(f_maxamount-f_minamount)", result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QString account = result.at(i).at(0);

            SKGAdvice ad;
            ad.setUUID("skgoperationplugin_close_minimum_limit|" % account);
            ad.setPriority(5);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Your account '%1' is close to the minimum limit", account));
            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."));
            output.push_back(ad);
        }
    }

    return output;
}

SKGError SKGOperationPlugin::executeAdviceCorrection(const QString& iAdviceIdentifier, int iSolution)
{
    if (m_currentBankDocument && iAdviceIdentifier.startsWith(QLatin1String("skgoperationplugin_duplicate|"))) {
        // Get parameters
        QString parameters = iAdviceIdentifier.right(iAdviceIdentifier.length() - 29);
        int pos = parameters.indexOf(';');
        QString num = parameters.left(pos);
        QString account = parameters.right(parameters.length() - 1 - pos);

        // Call operation plugin
        SKGMainPanel::getMainPanel()->openPage("skg://skrooge_operation_plugin/?title_icon=security-low&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Operations of '%1' with duplicate number %2", account, num)) %
                                               "&operationWhereClause=" % SKGServices::encodeForUrl("i_number=" % SKGServices::stringToSqlString(num) % " AND t_ACCOUNT='" % SKGServices::stringToSqlString(account) % '\''));
        return SKGError();
    } else if (m_currentBankDocument && iAdviceIdentifier.startsWith(QLatin1String("skgoperationplugin_notreconciliated|"))) {
        // Get parameters
        QString account = iAdviceIdentifier.right(iAdviceIdentifier.length() - 36);
        SKGMainPanel::getMainPanel()->openPage("skg://skrooge_operation_plugin/?currentPage=-1&modeInfoZone=1&account=" % SKGServices::encodeForUrl(account));
        return SKGError();
    }

    return SKGInterfacePlugin::executeAdviceCorrection(iAdviceIdentifier, iSolution);
}

void SKGOperationPlugin::onRemoveGroupWithOneOperation()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    {
        SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Remove groups with only one operation"), err);
        err = m_currentBankDocument->executeSqliteOrder("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");
    }

    // status bar
    IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Remove groups done.")))
    else {
        err.addError(ERR_FAIL, i18nc("Error message", "Remove groups failed"));
    }

    // Display error
    SKGMainPanel::displayErrorMessage(err);
}

void SKGOperationPlugin::onAlignComment()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    {
        SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Align comment of suboperations"), err);
        err = m_currentBankDocument->executeSqliteOrder("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)");
    }

    // status bar
    IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Comments aligned.")))
    else {
        err.addError(ERR_FAIL, i18nc("Error message", "Comments alignment failed"));
    }

    // Display error
    SKGMainPanel::displayErrorMessage(err);
}

void SKGOperationPlugin::onAlignDate()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    {
        SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Align date of suboperations"), err);
        err = m_currentBankDocument->executeSqliteOrder("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 AND (SELECT COUNT(1) FROM suboperation so2 WHERE so2.rd_operation_id=op.id)=1)");
    }

    // status bar
    IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Dates aligned.")))
    else {
        err.addError(ERR_FAIL, i18nc("Error message", "Dates alignment failed"));
    }

    // Display error
    SKGMainPanel::displayErrorMessage(err);
}

void SKGOperationPlugin::onMergeSubOperations()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    if (SKGMainPanel::getMainPanel() && m_currentBankDocument) {
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        if (nb >= 2) {
            SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Merge sub operations"), err);
            SKGOperationObject op(selection.at(0));
            for (int i = 1; !err && i < nb; ++i) {
                SKGOperationObject op2(selection.at(i));
                err = op.mergeSuboperations(op2);

                // Send message
                IFOKDO(err, op.getDocument()->sendMessage(i18nc("An information to the user", "The sub operations of '%1' have been merged in the operation '%2'", op2.getDisplayName(), op.getDisplayName()), SKGDocument::Hidden));
            }
        }
    }

    // status bar
    IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Operations merged.")))
    else {
        err.addError(ERR_FAIL, i18nc("Error message", "Merge failed"));
    }

    // Display error
    SKGMainPanel::displayErrorMessage(err);
}

#include "skgoperationplugin.moc"
