/***************************************************************************
 *   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 for KMY import / export.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgimportpluginkmy.h"

#include <KCompressionDevice>
#include <kpluginfactory.h>

#include <QDomDocument>
#include <qfileinfo.h>
#include <qmath.h>

#include "skgtraces.h"
#include "skgservices.h"
#include "skgbankincludes.h"
#include "skgobjectbase.h"
#include "skgimportexportmanager.h"
#include "skgpayeeobject.h"

/**
 * Operations treated.
 */
QSet<QString> SKGImportPluginKmy::m_opTreated;

/**
 * Map id / unit.
 */
QMap<QString, SKGUnitObject> SKGImportPluginKmy::m_mapIdUnit;

/**
 * Map id / account.
 */
QMap<QString, SKGAccountObject> SKGImportPluginKmy::m_mapIdAccount;

/**
 * Map id / category.
 */
QMap<QString, SKGCategoryObject> SKGImportPluginKmy::m_mapIdCategory;

/**
 * Map id / payee.
 */
QMap<QString, SKGPayeeObject> SKGImportPluginKmy::m_mapIdPayee;

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGImportPluginKmyFactory, registerPlugin<SKGImportPluginKmy>();)

SKGImportPluginKmy::SKGImportPluginKmy(QObject* iImporter, const QVariantList& iArg)
    : SKGImportPlugin(iImporter)
{
    SKGTRACEINFUNC(10);
    Q_UNUSED(iArg);
}

SKGImportPluginKmy::~SKGImportPluginKmy()
{}

bool SKGImportPluginKmy::isImportPossible()
{
    SKGTRACEINFUNC(10);
    return isExportPossible();
}

SKGError SKGImportPluginKmy::importSecurities(QDomElement& docElem)
{
    SKGError err;
    QDomElement securities = docElem.firstChildElement("SECURITIES");
    if (!err && !securities.isNull()) {
        SKGTRACEINRC(10, "SKGImportPluginKmy::importFile-SECURITIES", err);
        QDomNodeList securityList = securities.elementsByTagName("SECURITY");
        int nb = securityList.count();
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import units"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            QDomElement security = securityList.at(i).toElement();
            QString unitName = security.attribute("name");

            // We try a creation
            SKGUnitObject unitObj(m_importer->getDocument());
            SKGUnitObject::createCurrencyUnit(m_importer->getDocument(), unitName, unitObj);

            if (!err && !unitObj.getID()) {
                // Creation of unit
                err = unitObj.setName(unitName);
                QString symbol = security.attribute("symbol");
                if (symbol.isEmpty()) {
                    symbol = unitName;
                }
                IFOKDO(err, unitObj.setSymbol(symbol))
                IFOKDO(err, unitObj.setCountry(security.attribute("trading-market")))
                IFOKDO(err, unitObj.setType(SKGUnitObject::SHARE))
                IFOK(err) {
                    // Set pairs
                    QDomNodeList pairList = security.elementsByTagName("PAIR");
                    int nb2 = pairList.count();
                    for (int j = 0; !err && j < nb2; ++j) {
                        QDomElement pair = pairList.at(j).toElement();
                        if (pair.attribute("key").toLower() == "kmm-security-id") {
                            err = unitObj.setInternetCode(pair.attribute("value"));
                        }
                    }
                }
                IFOKDO(err, unitObj.save())
            }

            m_mapIdUnit[security.attribute("id")] = unitObj;

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    return err;
}

SKGError SKGImportPluginKmy::importPrices(QDomElement& docElem)
{
    SKGError err;
    QDomElement prices = docElem.firstChildElement("PRICES");
    if (!err && !prices.isNull()) {
        SKGTRACEINRC(10, "SKGImportPluginKmy::importFile-PRICES", err);
        QDomNodeList pricepairList = prices.elementsByTagName("PRICEPAIR");
        int nb = pricepairList.count();
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import units"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            QDomElement pricepair = pricepairList.at(i).toElement();

            SKGUnitObject unitObj = m_mapIdUnit[pricepair.attribute("from")];
            if (unitObj.getID()) {
                // Unit is existing
                QDomNodeList pricesList = pricepair.elementsByTagName("PRICE");
                int nb2 = pricesList.count();
                for (int j = 0; !err && j < nb2; ++j) {
                    QDomElement price = pricesList.at(j).toElement();

                    SKGUnitValueObject unitValObj;
                    err = unitObj.addUnitValue(unitValObj);
                    IFOKDO(err, unitValObj.setDate(QDate::fromString(price.attribute("date"), "yyyy-MM-dd")))
                    IFOKDO(err, unitValObj.setQuantity(toKmyValue(price.attribute("price"))))
                    IFOKDO(err, unitValObj.save(true, false))
                }
            }

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    return err;
}

SKGError SKGImportPluginKmy::importInstitutions(QMap<QString, SKGBankObject>& mapIdBank, QDomElement& docElem)
{
    SKGError err;
    QDomElement institutions = docElem.firstChildElement("INSTITUTIONS");
    if (!err && !institutions.isNull()) {
        SKGTRACEINRC(10, "SKGImportPluginKmy::importFile-INSTITUTIONS", err);
        QDomNodeList institutionList = institutions.elementsByTagName("INSTITUTION");
        int nb = institutionList.count();
        for (int i = 0; !err && i < nb; ++i) {
            // Get bank object
            QDomElement bank = institutionList.at(i).toElement();
            SKGBankObject bankObject(m_importer->getDocument());
            err = bankObject.setName(bank.attribute("name"));
            IFOKDO(err, bankObject.setNumber(bank.attribute("sortcode")))
            IFOKDO(err, bankObject.save())
            mapIdBank[bank.attribute("id")] = bankObject;
        }
    }
    return err;
}

SKGError SKGImportPluginKmy::importPayees(QMap<QString, SKGPayeeObject>& mapIdPayee, QDomElement& docElem)
{
    SKGError err;
    QDomElement payees = docElem.firstChildElement("PAYEES");
    if (!err && !payees.isNull()) {
        SKGTRACEINRC(10, "SKGImportPluginKmy::importFile-PAYEES", err);
        QDomNodeList payeeList = payees.elementsByTagName("PAYEE");
        int nb = payeeList.count();
        for (int i = 0; !err && i < nb; ++i) {
            // Get account object
            QDomElement payee = payeeList.at(i).toElement();
            QDomElement address = payee.firstChildElement("ADDRESS");
            SKGPayeeObject payeeObject;
            err = SKGPayeeObject::createPayee(m_importer->getDocument(), payee.attribute("name"), payeeObject);
            IFOK(err) {
                QString add = address.attribute("street") % ' ' % address.attribute("postcode") % ' ' % address.attribute("city") % ' ' % address.attribute("state") % ' ' % address.attribute("telephone");
                add.replace("  ", " ");
                err = payeeObject.setAddress(add.trimmed());
                IFOKDO(err, payeeObject.save())
            }
            IFOK(err) mapIdPayee[payee.attribute("id")] = payeeObject;
        }
    }
    return err;
}

SKGError SKGImportPluginKmy::importTransactions(QDomElement& docElem, SKGAccountObject& kmymoneyTemporaryAccount, QMap<QString, SKGPayeeObject>& mapIdPayee)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGImportPluginKmy::importFile-TRANSACTION", err);
    QDomNodeList transactionList = docElem.elementsByTagName("TRANSACTION");
    int nb = transactionList.count();
    err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import operations"), nb);
    for (int i = 0; !err && i < nb; ++i) {
        // Get operation object
        QDomElement operation = transactionList.at(i).toElement();

        SKGOperationObject operationObj;
        err = kmymoneyTemporaryAccount.addOperation(operationObj, true);
        IFOKDO(err, operationObj.setDate(QDate::fromString(operation.attribute("postdate"), "yyyy-MM-dd")))
        IFOKDO(err, operationObj.setComment(operation.attribute("memo")))
        IFOKDO(err, operationObj.setImported(true))
        IFOKDO(err, operationObj.setImportID("KMY-" % operation.attribute("id")))
        IFOK(err) {
            QString unitName = operation.attribute("commodity");
            if (unitName.isEmpty()) {
                unitName = "??";
            }
            SKGUnitObject unit(m_importer->getDocument());
            err = unit.setName(unitName);
            IFOKDO(err, unit.setSymbol(unitName))
            IFOK(err) {
                if (unit.exist()) {
                    err = unit.load();
                } else {
                    err = unit.save();
                }

                IFOKDO(err, operationObj.setUnit(unit))
            }
        }
        IFOKDO(err, operationObj.save(false, false))

        // Is it a schedule ?
        IFOK(err) {
            QDomElement recu = operation.parentNode().toElement();
            if (recu.tagName() == "SCHEDULED_TX") {
                // Yes ==> creation of the schedule
                IFOKDO(err, operationObj.load())
                IFOKDO(err, operationObj.setTemplate(true))
                IFOKDO(err, operationObj.save(true, false))

                // Yes ==> creation of the schedule
                SKGRecurrentOperationObject recuObj;
                IFOKDO(err, operationObj.addRecurrentOperation(recuObj))
                QDate firstDate = QDate::fromString(recu.attribute("startDate"), "yyyy-MM-dd");
                IFOKDO(err, recuObj.setDate(firstDate))
                IFOKDO(err, recuObj.autoWriteEnabled(recu.attribute("autoEnter") == "1"))
                IFOKDO(err, recuObj.setAutoWriteDays(0))

                int occu = SKGServices::stringToInt(recu.attribute("occurenceMultiplier"));
                QString occuType = recu.attribute("occurence");  // krazy:exclude=spelling
                SKGRecurrentOperationObject::PeriodUnit period;
                if (occuType == "32") {
                    period = SKGRecurrentOperationObject::MONTH;
                } else if (occuType == "1") {
                    period = SKGRecurrentOperationObject::DAY;
                    IFOKDO(err, recuObj.timeLimit(true))
                    IFOKDO(err, recuObj.setTimeLimit(1))
                } else if (occuType == "2") {
                    period = SKGRecurrentOperationObject::DAY;
                } else if (occuType == "4") {
                    period = SKGRecurrentOperationObject::WEEK;
                } else if (occuType == "18") {
                    period = SKGRecurrentOperationObject::WEEK;
                    occu *= 2;
                } else {
                    period = SKGRecurrentOperationObject::YEAR;
                }


                IFOKDO(err, recuObj.setPeriodIncrement(occu))
                IFOKDO(err, recuObj.setPeriodUnit(period))

                IFOK(err) {
                    QString endDate = recu.attribute("endDate");
                    if (!endDate.isEmpty()) {
                        IFOKDO(err, recuObj.timeLimit(true))
                        IFOKDO(err, recuObj.setTimeLimit(QDate::fromString(recu.attribute("endDate"), "yyyy-MM-dd")))
                    }
                }

                IFOKDO(err, recuObj.save(true, false))
            }
        }

        // Get splits
        bool parentSet = false;
        double quantity = 0;
        QDomElement splits = operation.firstChildElement("SPLITS");

        QList<QDomNode> suboperationsList;
        int nbSuboperations = 0;
        {
            QDomNodeList suboperationsListTmp = splits.elementsByTagName("SPLIT");
            nbSuboperations = suboperationsListTmp.count();

            for (int j = 0; !err && j < nbSuboperations; ++j) {
                QDomElement suboperation = suboperationsListTmp.at(j).toElement();
                if (m_mapIdCategory.contains(suboperation.attribute("account"))) {
                    suboperationsList.push_front(suboperation);
                } else {
                    suboperationsList.push_back(suboperation);
                }
            }
        }

        SKGSubOperationObject suboperationObj;
        for (int j = 0; !err && j < nbSuboperations; ++j) {
            QDomElement suboperation = suboperationsList.at(j).toElement();

            // Set operation attributes
            IFOKDO(err, operationObj.setPayee(mapIdPayee[suboperation.attribute("payee")]))
            if (!err && operationObj.getComment().isEmpty()) {
                err = operationObj.setComment(suboperation.attribute("memo"));
            }
            IFOKDO(err, operationObj.setNumber(SKGServices::stringToInt(suboperation.attribute("number"))))

            // Set state
            if (operationObj.getStatus() == SKGOperationObject::NONE) {
                QString val = suboperation.attribute("reconcileflag");
                IFOKDO(err, operationObj.setStatus(val == "1" ? SKGOperationObject::POINTED : val == "2" ? SKGOperationObject::CHECKED : SKGOperationObject::NONE))
            }
            IFOKDO(err, operationObj.save())

            if (m_mapIdCategory.contains(suboperation.attribute("account"))) {
                // It is a split on category
                SKGCategoryObject cat = m_mapIdCategory[suboperation.attribute("account")];

                double q = -toKmyValue(suboperation.attribute("shares"));
                if (!err && (!suboperationObj.getID() || suboperationObj.getQuantity() != q)) {
                    err = operationObj.addSubOperation(suboperationObj);
                }
                IFOKDO(err, suboperationObj.setQuantity(q))
                IFOKDO(err, suboperationObj.setCategory(cat))
                IFOKDO(err, suboperationObj.setComment(suboperation.attribute("memo")))
                IFOKDO(err, suboperationObj.save(true, false))
            } else {
                QString accountSubOp = suboperation.attribute("account");
                if (!accountSubOp.isEmpty()) {
                    if (!m_mapIdAccount.contains(accountSubOp)) {
                        // Set as initial balance
                        IFOKDO(err, operationObj.setAttribute("d_date", "0000-00-00"))
                        IFOKDO(err, operationObj.setStatus(SKGOperationObject::CHECKED))
                        IFOKDO(err, operationObj.save(true, false))

                        if (!err && !suboperationObj.getID()) {
                            err = operationObj.addSubOperation(suboperationObj);
                        }

                        IFOKDO(err, suboperationObj.setAttribute("d_date", "0000-00-00"))
                        IFOKDO(err, suboperationObj.save(true, false))
                    } else {
                        // It is a transfer of account
                        SKGAccountObject act = m_mapIdAccount[suboperation.attribute("account")];

                        if (parentSet) {
                            // If the parent is already set, it means that is a transfer
                            SKGOperationObject operationObj2;
                            IFOKDO(err, act.addOperation(operationObj2, true))
                            IFOKDO(err, operationObj2.setTemplate(operationObj.isTemplate()))
                            IFOKDO(err, operationObj2.setDate(operationObj.getDate()))
                            IFOKDO(err, operationObj2.setNumber(operationObj.getNumber()))
                            IFOKDO(err, operationObj2.setComment(operationObj.getComment()))
                            SKGPayeeObject payeeObject;
                            operationObj.getPayee(payeeObject);
                            IFOKDO(err, operationObj2.setPayee(payeeObject))
                            IFOK(err) {
                                QString val = suboperation.attribute("reconcileflag");
                                err = operationObj2.setStatus(val == "1" ? SKGOperationObject::POINTED : val == "2" ? SKGOperationObject::CHECKED : SKGOperationObject::NONE);
                            }
                            IFOKDO(err, operationObj2.setImported(true))
                            IFOKDO(err, operationObj2.setImportID(operationObj.getImportID()))

                            SKGUnitObject unit = m_mapIdUnit[suboperation.attribute("account")];
                            if (unit.getID()) {
                                IFOKDO(err, operationObj2.setUnit(unit))
                            } else {
                                IFOKDO(err, operationObj.getUnit(unit))
                                IFOKDO(err, operationObj2.setUnit(unit))
                            }
                            IFOKDO(err, operationObj2.save())
                            IFOKDO(err, operationObj2.setGroupOperation(operationObj))
                            IFOKDO(err, operationObj2.save())

                            // Create sub operation on operationObj2
                            SKGSubOperationObject suboperationObj2;
                            IFOKDO(err, operationObj2.addSubOperation(suboperationObj2))
                            IFOK(err) {
                                // We must take the quality of the split having an action
                                quantity = toKmyValue(suboperation.attribute("shares"));
                                err = suboperationObj2.setQuantity(quantity);
                            }
                            IFOKDO(err, suboperationObj2.setComment(suboperation.attribute("memo")))
                            IFOKDO(err, suboperationObj2.save(true, false))
                        } else {
                            // We set the parent
                            if (Q_LIKELY(!err) && !act.getID()) {
                                err = SKGError(ERR_FAIL, i18nc("Error message", "Account with identifier %1 not found", suboperation.attribute("account")));
                            }
                            IFOKDO(err, operationObj.setParentAccount(act, true))
                            IFOKDO(err, operationObj.save())

                            // Compute quantity
                            quantity = toKmyValue(suboperation.attribute("shares"));

                            // Create sub operation on operationObj
                            quantity -= SKGServices::stringToDouble(operationObj.getAttribute("f_QUANTITY"));
                            if (quantity != 0) {
                                IFOKDO(err, operationObj.addSubOperation(suboperationObj))
                                IFOKDO(err, suboperationObj.setQuantity(quantity))
                                IFOKDO(err, suboperationObj.setComment(suboperation.attribute("memo")))
                                IFOKDO(err, suboperationObj.save(true, false))
                            }

                            parentSet = true;
                        }
                    }
                }
            }
        }

        if (!err && i % 500 == 0) {
            err = m_importer->getDocument()->executeSqliteOrder("ANALYZE");
        }

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

    SKGENDTRANSACTION(m_importer->getDocument(),  err);

    return err;
}

SKGError SKGImportPluginKmy::importBudget(QDomElement& docElem)
{
    SKGError err;
    QDomElement budgets = docElem.firstChildElement("BUDGETS");
    if (!err && !budgets.isNull()) {
        SKGTRACEINRC(10, "SKGImportPluginKmy::importFile-BUDGETS", err);
        // Build cache of categories
        QMap<int, bool> catExpense;
        {
            SKGStringListList list;
            err = m_importer->getDocument()->executeSelectSqliteOrder("SELECT id, t_TYPEEXPENSE FROM v_category_display", list);
            int nb = list.count();
            for (int i = 1; i < nb; ++i) {
                catExpense[SKGServices::stringToInt(list.at(i).at(0))] = (list.at(i).at(0) == "-");
            }
        }

        QDomNodeList budgetList = budgets.elementsByTagName("BUDGET");
        int nb = budgetList.count();
        IFOKDO(err, m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import budgets"), nb))
        for (int i = 0; !err && i < nb; ++i) {
            QDomElement budget = budgetList.at(i).toElement();
            QDomNodeList accountList = budget.elementsByTagName("ACCOUNT");
            int nb2 = accountList.count();
            for (int j = 0; !err && j < nb2; ++j) {
                QDomElement account = accountList.at(j).toElement();
                SKGCategoryObject cat = m_mapIdCategory[account.attribute("id")];
                QString budgetlevel = account.attribute("budgetlevel");

                QDomNodeList periodList = account.elementsByTagName("PERIOD");
                int nb3 = periodList.count();
                for (int k = 0; !err && k < nb3; ++k) {
                    QDomElement period = periodList.at(k).toElement();

                    double q = toKmyValue(period.attribute("amount"));

                    // Are we able to find to sign with the category ?
                    if (catExpense[cat.getID()]) {
                        q = -q;
                    }

                    QStringList dates = SKGServices::splitCSVLine(period.attribute("start"), '-');
                    if (dates.count() == 3) {
                        // We try a creation
                        for (int m = 1; !err && m <= (budgetlevel == "monthly" ? 12 : 1); ++m) {
                            SKGBudgetObject budget2(m_importer->getDocument());
                            err = budget2.setCategory(cat);
                            IFOKDO(err, budget2.setBudgetedAmount(q))
                            IFOKDO(err, budget2.setYear(SKGServices::stringToDouble(dates.at(0))))
                            IFOKDO(err, budget2.setMonth(budgetlevel == "monthbymonth" ? SKGServices::stringToDouble(dates.at(1)) :
                                                         budgetlevel == "yearly" ? 0 : m));
                            IFOKDO(err, budget2.save(true, false))
                        }
                    }
                }
            }

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    return err;
}

SKGError SKGImportPluginKmy::importAccounts(SKGBankObject& bank, SKGAccountObject& kmymoneyTemporaryAccount, QMap<QString, SKGBankObject>& mapIdBank, QDomElement& docElem)
{
    SKGError err;
    IFOKDO(err, m_importer->getDocument()->addOrModifyAccount("KMYMONEY-TEMPORARY-ACCOUNT", "", "KMYMONEY"))
    IFOKDO(err, bank.setName("KMYMONEY"))
    IFOKDO(err, bank.load())
    IFOKDO(err, kmymoneyTemporaryAccount.setName("KMYMONEY-TEMPORARY-ACCOUNT"))
    IFOKDO(err, kmymoneyTemporaryAccount.load())

    QDomElement accounts = docElem.firstChildElement("ACCOUNTS");
    if (!err && !accounts.isNull()) {
        SKGTRACEINRC(10, "SKGImportPluginKmy::importFile-ACCOUNTS", err);
        QDomNodeList accountList = accounts.elementsByTagName("ACCOUNT");
        int nb = accountList.count();
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import accounts"), nb);
        QMap<QString, QString> type15Account;
        QMap<QString, QString> type15Unit;
        for (int i = 0; !err && i < nb; ++i) {
            // Get account object
            QDomElement account = accountList.at(i).toElement();
            QString type = account.attribute("type");
            if (type == "15") {
                // Actions: the real account is the parent
                type15Account[account.attribute("id")] = account.attribute("parentaccount");
                type15Unit[account.attribute("id")] = account.attribute("currency");
            } else if (type != "12" && type != "13" && type != "16") {
                // Get the bank
                QString bankId = account.attribute("institution");
                SKGBankObject bankObj = (bankId.isEmpty() ? bank : mapIdBank[account.attribute("institution")]);

                // Creation of the account
                SKGAccountObject accountObj;
                IFOKDO(err, bankObj.addAccount(accountObj))
                IFOKDO(err, accountObj.setName(account.attribute("name")))
                IFOKDO(err, accountObj.setNumber(account.attribute("number")))
                IFOKDO(err, accountObj.setComment(account.attribute("description")))
                IFOK(err) {
                    SKGAccountObject::AccountType typeA = (type == "4" ? SKGAccountObject::CREDITCARD : (type == "7" ? SKGAccountObject::INVESTMENT : (type == "9" ? SKGAccountObject::ASSETS : (type == "3" ? SKGAccountObject::WALLET : (type == "10" ? SKGAccountObject::LOAN : SKGAccountObject::CURRENT)))));
                    err = accountObj.setType(typeA);
                }
                IFOK(err) {
                    // Set pairs
                    QDomNodeList pairList = account.elementsByTagName("PAIR");
                    int nb2 = pairList.count();
                    for (int j = 0; !err && j < nb2; ++j) {
                        QDomElement pair = pairList.at(j).toElement();
                        if (pair.attribute("key").toLower() == "mm-closed" && pair.attribute("value").toLower() == "yes") {
                            err = accountObj.setClosed(true);
                        } else if (pair.attribute("key").toLower() == "preferredaccount" && pair.attribute("value").toLower() == "yes") {
                            err = accountObj.bookmark(true);
                        } else if (pair.attribute("key").toLower() == "minbalanceabsolute") {
                            err = accountObj.setMinLimitAmount(toKmyValue(pair.attribute("value")));
                            IFOKDO(err, accountObj.minLimitAmountEnabled(true))
                        } else if (pair.attribute("key").toLower() == "maxcreditabsolute") {
                            err = accountObj.setMaxLimitAmount(-toKmyValue(pair.attribute("value")));
                            IFOKDO(err, accountObj.maxLimitAmountEnabled(true))
                        }
                    }
                }
                IFOKDO(err, accountObj.save())

                // Change parent bank in case of ASSETS
                if (accountObj.getType() == SKGAccountObject::WALLET) {
                    // Get blank bank
                    SKGBankObject blankBank(m_importer->getDocument());
                    IFOKDO(err, blankBank.setName(""))
                    if (blankBank.exist()) {
                        err = blankBank.load();
                    } else {
                        err = blankBank.save();
                    }
                    IFOKDO(err, accountObj.setBank(blankBank))
                    IFOKDO(err, accountObj.save())
                }

                m_mapIdAccount[account.attribute("id")] = accountObj;
            } else if (type != "16") {
                // Create category
                SKGCategoryObject cat = m_mapIdCategory[account.attribute("id")];
                if (cat.getID()) {
                    // Already existing ==> we must set the right name
                    err = cat.setName(account.attribute("name"));
                    IFOKDO(err, cat.save())
                    m_mapIdCategory[account.attribute("id")] = cat;
                } else {
                    // We must create it
                    cat = SKGCategoryObject(m_importer->getDocument());
                    err = cat.setName(account.attribute("name"));
                    IFOKDO(err, cat.save())
                    m_mapIdCategory[account.attribute("id")] = cat;
                }

                // Create sub categories
                QDomNodeList subaccountList = account.elementsByTagName("SUBACCOUNT");
                int nb2 = subaccountList.count();
                for (int j = 0; !err && j < nb2; ++j) {
                    QDomElement subaccount = subaccountList.at(j).toElement();

                    // Is child already existing ?
                    SKGCategoryObject cat2 = m_mapIdCategory[subaccount.attribute("id")];
                    if (cat2.getID()) {
                        // Yes ==> reparent
                        err = cat2.setParentCategory(cat);
                        IFOKDO(err, cat2.save(true, false))
                    } else {
                        // No ==> create
                        IFOKDO(err, cat.addCategory(cat2))
                        IFOKDO(err, cat2.setName(subaccount.attribute("id")))
                        IFOKDO(err, cat2.save())
                        m_mapIdCategory[subaccount.attribute("id")] = cat2;
                    }
                }
            }

            QStringList list = type15Account.keys();
            foreach(const QString & k, list) {
                m_mapIdAccount[k] = m_mapIdAccount[type15Account[k]];
                m_mapIdUnit[account.attribute("id")] = m_mapIdUnit[account.attribute("currency")];
            }

            list = type15Unit.keys();
            foreach(const QString & k, list) {
                m_mapIdUnit[k] = m_mapIdUnit[type15Unit[k]];
            }

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    return err;
}

SKGError SKGImportPluginKmy::importFile()
{
    if (!m_importer) {
        return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters"));
    }

    SKGError err;
    SKGTRACEINFUNCRC(2, err);

    // Initialisation
    m_mapIdUnit.clear();
    m_mapIdAccount.clear();
    m_mapIdCategory.clear();
    m_mapIdPayee.clear();

    // Open file
    KCompressionDevice file(m_importer->getLocalFileName(), KCompressionDevice::GZip);
    if (!file.open(QIODevice::ReadOnly)) {
        err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Open file '%1' failed", m_importer->getFileName().toDisplayString()));
    } else {
        QDomDocument doc;

        // Set the file without uncompression
        QString errorMsg;
        int errorLine = 0;
        int errorCol = 0;
        bool contentOK = doc.setContent(file.readAll(), &errorMsg, &errorLine, &errorCol);
        file.close();

        if (!contentOK) {
            err.setReturnCode(ERR_ABORT).setMessage(i18nc("Error message",  "%1-%2: '%3'", errorLine, errorCol, errorMsg)).addError(ERR_INVALIDARG, i18nc("Error message",  "Invalid XML content in file '%1'", m_importer->getFileName().toDisplayString()));
        } else {
            // Get root
            QDomElement docElem = doc.documentElement();
            err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import %1 file", "KMY"), 8);

            // Step 1-Get units
            IFOKDO(err, importSecurities(docElem));
            IFOKDO(err, m_importer->getDocument()->stepForward(1))

            // Step 2-Get units prices
            IFOKDO(err, importPrices(docElem));
            IFOKDO(err, m_importer->getDocument()->stepForward(2))

            // Step 3-Create banks
            QMap<QString, SKGBankObject> mapIdBank;
            IFOKDO(err, importInstitutions(mapIdBank, docElem));
            IFOKDO(err, m_importer->getDocument()->stepForward(3))

            // Step 4-Create account and categories
            // Create bank and temporary account for kmymoney import
            SKGAccountObject kmymoneyTemporaryAccount(m_importer->getDocument());
            SKGBankObject bank(m_importer->getDocument());
            IFOKDO(err, importAccounts(bank, kmymoneyTemporaryAccount, mapIdBank, docElem));
            IFOKDO(err, m_importer->getDocument()->stepForward(4))

            // Step 5-Read payees
            QMap<QString, SKGPayeeObject> mapIdPayee;
            IFOKDO(err, importPayees(mapIdPayee, docElem));
            IFOKDO(err, m_importer->getDocument()->stepForward(5))

            // Step 6-Create operations
            IFOKDO(err, importTransactions(docElem, kmymoneyTemporaryAccount, mapIdPayee));
            IFOKDO(err, m_importer->getDocument()->stepForward(6))

            // Step 7-Get budgets
            IFOKDO(err, importBudget(docElem));
            IFOKDO(err, m_importer->getDocument()->stepForward(7))

            // Step 8-Remove useless account and temporary account
            {
                IFOKDO(err, kmymoneyTemporaryAccount.remove(false, true))
                IFOKDO(err, m_importer->getDocument()->executeSqliteOrder("DELETE FROM account WHERE rd_bank_id=" % SKGServices::intToString(bank.getID()) % " AND (SELECT COUNT(1) FROM operation WHERE operation.rd_account_id=account.id)=0"))
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(8))

            SKGENDTRANSACTION(m_importer->getDocument(),  err);

            IFOKDO(err, m_importer->getDocument()->executeSqliteOrder("ANALYZE"))
        }
    }

    // Clean
    m_mapIdUnit.clear();
    m_mapIdAccount.clear();
    m_mapIdCategory.clear();
    m_mapIdPayee.clear();

    return err;
}

bool SKGImportPluginKmy::isExportPossible()
{
    SKGTRACEINFUNC(10);
    return (!m_importer ? true : m_importer->getFileNameExtension() == "KMY");
}

SKGError SKGImportPluginKmy::exportHeader(QDomDocument& doc, QDomElement& root)
{
    SKGError err;
    QDomElement fileindo = doc.createElement("FILEINFO");
    root.appendChild(fileindo);

    {
        // <CREATION_DATE>
        QDomElement creation_date = doc.createElement("CREATION_DATE");
        fileindo.appendChild(creation_date);
        creation_date.setAttribute("date", SKGServices::dateToSqlString(QDateTime::currentDateTime()));

        // <LAST_MODIFIED_DATE>
        QDomElement last_modified_date = doc.createElement("LAST_MODIFIED_DATE");
        fileindo.appendChild(last_modified_date);
        last_modified_date.setAttribute("date", SKGServices::dateToSqlString(QDateTime::currentDateTime()));

        // <VERSION>
        QDomElement version = doc.createElement("VERSION");
        fileindo.appendChild(version);
        version.setAttribute("id", "1");

        // <FIXVERSION>
        QDomElement fixversion = doc.createElement("FIXVERSION");
        fileindo.appendChild(fixversion);
        fixversion.setAttribute("id", "2");
    }

    // <USER>
    QDomElement user = doc.createElement("USER");
    root.appendChild(user);
    user.setAttribute("email", "");
    user.setAttribute("name", "");
    {
        // ADDRESS
        QDomElement address = doc.createElement("ADDRESS");
        user.appendChild(address);
        address.setAttribute("street", "");
        address.setAttribute("zipcode", "");
        address.setAttribute("county", "");
        address.setAttribute("city", "");
        address.setAttribute("telephone", "");
    }
    return err;
}

SKGError SKGImportPluginKmy::exportSecurities(QDomDocument& doc, QDomElement& root, const QString& stdUnit)
{
    SKGError err;
    QDomElement securities = doc.createElement("SECURITIES");
    root.appendChild(securities);

    QDomElement currencies = doc.createElement("CURRENCIES");
    root.appendChild(currencies);

    SKGObjectBase::SKGListSKGObjectBase objects;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_unit", "t_type!='I'", objects))
    int nb = objects.count();
    securities.setAttribute("count", SKGServices::intToString(nb));

    QStringList importedCurrency;
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export units"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGUnitObject obj(objects.at(i));
            if (obj.getType() == SKGUnitObject::SHARE || obj.getType() == SKGUnitObject::OBJECT) {
                QDomElement security = doc.createElement("SECURITY");
                securities.appendChild(security);

                SKGUnitObject parentUnit;
                obj.getUnit(parentUnit);
                QString unitP = SKGUnitObject::getInternationalCode(parentUnit.getName());
                if (unitP.isEmpty()) {
                    unitP = stdUnit;
                }

                security.setAttribute("id", obj.getName());
                security.setAttribute("trading-currency", unitP);
                security.setAttribute("saf", "100000");
                security.setAttribute("symbol", obj.getSymbol());
                security.setAttribute("trading-market", obj.getCountry());
                security.setAttribute("type", "4");
                security.setAttribute("name", obj.getName());

                QString internetCode = obj.getInternetCode();
                if (!internetCode.isEmpty()) {
                    QDomElement keyvaluepairs2 = doc.createElement("KEYVALUEPAIRS");
                    security.appendChild(keyvaluepairs2);

                    QDomElement pair1 = doc.createElement("PAIR");
                    keyvaluepairs2.appendChild(pair1);
                    pair1.setAttribute("key", "kmm-online-source");
                    pair1.setAttribute("value", "Yahoo");

                    QDomElement pair2 = doc.createElement("PAIR");
                    keyvaluepairs2.appendChild(pair2);
                    pair2.setAttribute("key", "kmm-security-id");
                    pair2.setAttribute("value", internetCode);
                }
            } else {
                QDomElement currency = doc.createElement("CURRENCY");
                currencies.appendChild(currency);

                QString unit = SKGUnitObject::getInternationalCode(obj.getName());
                if (unit.isEmpty()) {
                    unit = obj.getName();
                }

                currency.setAttribute("saf", "100");
                currency.setAttribute("symbol", obj.getSymbol());
                currency.setAttribute("type", "3");
                currency.setAttribute("id", unit);
                currency.setAttribute("name", obj.getName());
                currency.setAttribute("ppu", "100");
                currency.setAttribute("scf", "100");

                importedCurrency.push_back(obj.getSymbol());
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
        }

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }

    // <CURRENCIES>
    QStringList units = SKGUnitObject::getListofKnownCurrencies(false);
    nb = units.count();
    int nbreal = 0;
    for (int i = 0; i < nb; ++i) {
        SKGServices::SKGUnitInfo info = SKGUnitObject::getUnitInfo(units.at(i));
        if (info.Name != info.Symbol && !importedCurrency.contains(info.Symbol)) {
            QDomElement currency = doc.createElement("CURRENCY");
            currencies.appendChild(currency);
            currency.setAttribute("saf", "100");
            currency.setAttribute("symbol", info.Symbol);
            currency.setAttribute("type", "3");
            currency.setAttribute("id", SKGUnitObject::getInternationalCode(info.Name));
            currency.setAttribute("name", info.Name);
            currency.setAttribute("ppu", "100");
            currency.setAttribute("scf", "100");

            ++nbreal;
        }
    }
    currencies.setAttribute("count", SKGServices::intToString(nbreal));

    // <PRICES>
    QDomElement prices = doc.createElement("PRICES");
    root.appendChild(prices);
    IFOKDO(err, m_importer->getDocument()->getObjects("v_unit", "t_type='S'", objects))
    nb = objects.count();
    prices.setAttribute("count", SKGServices::intToString(nb));
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export units"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGUnitObject obj(objects.at(i));
            QDomElement pricepair = doc.createElement("PRICEPAIR");
            prices.appendChild(pricepair);

            QString unitP = SKGUnitObject::getInternationalCode(obj.getName());
            if (unitP.isEmpty()) {
                unitP = stdUnit;
            }

            pricepair.setAttribute("from", obj.getName());
            pricepair.setAttribute("to", unitP);

            SKGObjectBase::SKGListSKGObjectBase unitValues;
            err = obj.getUnitValues(unitValues);
            int nb2 = unitValues.count();
            for (int j = 0; !err && j < nb2; ++j) {
                QDomElement price = doc.createElement("PRICE");
                pricepair.appendChild(price);

                SKGUnitValueObject unitval(unitValues.at(j));
                price.setAttribute("price", SKGImportPluginKmy::kmyValue(unitval.getQuantity()));
                price.setAttribute("source", "Utilisateur");
                price.setAttribute("date", SKGServices::dateToSqlString(QDateTime(unitval.getDate())));
            }

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }

    // <REPORTS>
    QDomElement reports = doc.createElement("REPORTS");
    root.appendChild(reports);
    reports.setAttribute("count", "0");

    return err;
}

SKGError SKGImportPluginKmy::exportBudgets(QDomDocument& doc, QDomElement& root)
{
    SKGError err;
    QDomElement budgets = doc.createElement("BUDGETS");
    root.appendChild(budgets);

    SKGObjectBase::SKGListSKGObjectBase objects;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_budget", "1=1 ORDER BY i_year, i_month", objects))
    int nb = objects.count();
    int nbBudgets = 0;
    int currentYear = 0;
    QDomElement budget;

    QMap<QString, QDomElement> mapCatAccount;
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export budgets"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGBudgetObject obj(objects.at(i));
            SKGCategoryObject cat;
            obj.getCategory(cat);
            QString catId = getKmyUniqueIdentifier(cat);
            int year = obj.getYear();
            QString yearString = SKGServices::intToString(year);
            int month = obj.getMonth();
            QString monthString = SKGServices::intToString(month);
            if (monthString.isEmpty()) {
                monthString = '0' % monthString;
            }
            if (currentYear != year) {
                budget = doc.createElement("BUDGET");
                budgets.appendChild(budget);
                budget.setAttribute("version", "2");
                budget.setAttribute("id", yearString);
                budget.setAttribute("start", yearString % "-01-01");
                budget.setAttribute("name", yearString);

                currentYear = year;
                mapCatAccount.clear();
                ++nbBudgets;
            }

            QDomElement account = mapCatAccount[catId];
            if (account.isNull() && !catId.isEmpty()) {
                account = doc.createElement("ACCOUNT");
                budget.appendChild(account);
                account.setAttribute("budgetsubaccounts", "0");
                account.setAttribute("id", catId);
                mapCatAccount[catId] = account;
            }
            if (!account.isNull()) {
                account.setAttribute("budgetlevel", obj.getMonth() == 0 ? "yearly" : "monthbymonth");

                QDomElement period = doc.createElement("PERIOD");
                account.appendChild(period);
                period.setAttribute("amount", SKGImportPluginKmy::kmyValue(qAbs(obj.getBudgetedAmount())));
                period.setAttribute("start", yearString % '-' % (obj.getMonth() == 0 ? "01" : monthString) % "-01");
            }

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    budgets.setAttribute("count", SKGServices::intToString(nbBudgets));
    return err;
}

SKGError SKGImportPluginKmy::exportSchedules(QDomDocument& doc, QDomElement& root)
{
    SKGError err;
    QDomElement schedules = doc.createElement("SCHEDULES");
    root.appendChild(schedules);

    SKGObjectBase::SKGListSKGObjectBase objects;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_recurrentoperation", "", objects))
    int nb = objects.count();
    schedules.setAttribute("count", SKGServices::intToString(nb));
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export scheduled operations"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGRecurrentOperationObject obj(objects.at(i));
            SKGOperationObject op;
            err = obj.getParentOperation(op);
            IFOK(err) {
                QDomElement scheduled_tx = doc.createElement("SCHEDULED_TX");
                schedules.appendChild(scheduled_tx);

                scheduled_tx.setAttribute("id", getKmyUniqueIdentifier(obj));
                scheduled_tx.setAttribute("name", getKmyUniqueIdentifier(obj));
                scheduled_tx.setAttribute("startDate", obj.getAttribute("d_date"));
                scheduled_tx.setAttribute("lastPayment", obj.getAttribute("d_date"));
                bool autoEnter = false;
                obj.autoWriteEnabled(autoEnter);
                scheduled_tx.setAttribute("autoEnter", autoEnter  ? "1" : "0");

                QString occuType;
                int occu = obj.getPeriodIncrement();
                SKGRecurrentOperationObject::PeriodUnit punit = obj.getPeriodUnit();
                if (punit == SKGRecurrentOperationObject::MONTH) {
                    occuType = "32";
                } else if (punit == SKGRecurrentOperationObject::WEEK) {
                    occuType = '4';
                } else if (punit == SKGRecurrentOperationObject::DAY) {
                    occuType = '2';
                } else {
                    occuType = "16384";
                }

                scheduled_tx.setAttribute("occurenceMultiplier", SKGServices::intToString(occu));
                scheduled_tx.setAttribute("occurence", occuType);    // krazy:exclude=spelling
                scheduled_tx.setAttribute("weekendOption", "0");
                scheduled_tx.setAttribute("paymentType", "1");
                QChar type = '1';
                SKGOperationObject op2;
                if (op.isTransfer(op2)) {
                    type = '4';
                } else if (op.getCurrentAmount() > 0) {
                    type = '2';
                }
                scheduled_tx.setAttribute("type", type);
                scheduled_tx.setAttribute("fixed", "1");

                QString endDate;
                if (obj.hasTimeLimit()) {
                    QDate firstDate = obj.getDate();

                    // We must compute the date
                    int p = occu * (obj.getTimeLimit() - 1);
                    if (punit == SKGRecurrentOperationObject::DAY) {
                        firstDate = firstDate.addDays(p);
                    } else if (punit == SKGRecurrentOperationObject::MONTH) {
                        firstDate = firstDate.addMonths(p);
                    } else if (punit == SKGRecurrentOperationObject::YEAR) {
                        firstDate = firstDate.addYears(p);
                    }

                    endDate = firstDate.toString("yyyy-MM-dd");
                }
                scheduled_tx.setAttribute("endDate", endDate);

                err = exportOperation(op, doc, scheduled_tx);
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
        }

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    return err;
}

SKGError SKGImportPluginKmy::exportTransactions(QDomDocument& doc, QDomElement& root, const QString& stdUnit)
{
    SKGError err;
    QDomElement transactions = doc.createElement("TRANSACTIONS");
    root.appendChild(transactions);

    SKGObjectBase::SKGListSKGObjectBase objects;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_operation", "t_template='N' ORDER BY d_date DESC", objects))
    int nb = objects.count();
    transactions.setAttribute("count", SKGServices::intToString(nb));
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export operations"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGOperationObject op(objects.at(i));
            err = exportOperation(op, doc, transactions);
            IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
        }

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }

    // <KEYVALUEPAIRS>
    QDomElement keyvaluepairs = doc.createElement("KEYVALUEPAIRS");
    root.appendChild(keyvaluepairs);
    {
        QDomElement pair = doc.createElement("PAIR");
        keyvaluepairs.appendChild(pair);
        pair.setAttribute("key", "kmm-baseCurrency");
        pair.setAttribute("value", stdUnit);
    }
    return err;
}

SKGError SKGImportPluginKmy::exportPayees(QDomDocument& doc, QDomElement& root)
{
    SKGError err;
    QDomElement payees = doc.createElement("PAYEES");
    root.appendChild(payees);

    SKGObjectBase::SKGListSKGObjectBase listPayees;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_payee", "", listPayees))
    int nb = listPayees.count();
    payees.setAttribute("count", SKGServices::intToString(nb));
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export payees"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGPayeeObject payeeObject(listPayees.at(i));
            QDomElement payee = doc.createElement("PAYEE");
            payees.appendChild(payee);

            payee.setAttribute("matchingenabled", "0");
            payee.setAttribute("id", getKmyUniqueIdentifier(payeeObject));
            payee.setAttribute("name", payeeObject.getName());
            payee.setAttribute("email", "");
            payee.setAttribute("reference", "");

            QDomElement address = doc.createElement("ADDRESS");
            payee.appendChild(address);

            address.setAttribute("street", payeeObject.getAddress());
            address.setAttribute("postcode", "");
            address.setAttribute("zip", "");
            address.setAttribute("city", "");
            address.setAttribute("telephone", "");
            address.setAttribute("state", "");

            m_mapIdPayee[SKGServices::intToString(i + 1)] = payeeObject;

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    return err;
}

SKGError SKGImportPluginKmy::exportInstitutions(QDomDocument& doc, QDomElement& root)
{
    SKGError err;
    QDomElement institutions = doc.createElement("INSTITUTIONS");
    root.appendChild(institutions);
    SKGObjectBase::SKGListSKGObjectBase objects;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_bank", "EXISTS(SELECT 1 FROM account WHERE account.rd_bank_id=v_bank.id)", objects))
    int nb = objects.count();
    institutions.setAttribute("count", SKGServices::intToString(nb));
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export banks"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGBankObject obj(objects.at(i));
            QDomElement institution = doc.createElement("INSTITUTION");
            institutions.appendChild(institution);

            institution.setAttribute("id", getKmyUniqueIdentifier(obj));
            institution.setAttribute("name", obj.getName());
            institution.setAttribute("sortcode", obj.getNumber());
            institution.setAttribute("manager", "");

            QDomElement address = doc.createElement("ADDRESS");
            institution.appendChild(address);

            address.setAttribute("street", "");
            address.setAttribute("zip", "");
            address.setAttribute("city", "");
            address.setAttribute("telephone", "");

            QDomElement accountids = doc.createElement("ACCOUNTIDS");
            institution.appendChild(accountids);

            SKGObjectBase::SKGListSKGObjectBase accounts;
            err = obj.getAccounts(accounts);
            int nb2 = accounts.count();
            for (int j = 0; !err && j < nb2; ++j) {
                QDomElement accountid = doc.createElement("ACCOUNTID");
                accountids.appendChild(accountid);

                accountid.setAttribute("id", getKmyUniqueIdentifier(accounts.at(j)));
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
        }

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }

    return err;
}

SKGError SKGImportPluginKmy::exportCategories(QDomDocument& doc, QDomElement& accounts, const QString& stdUnit, QDomElement& accountIncome, QDomElement& accountExpense, int nbAccount)
{
    // The v_category_display are retrieved to improve performances of getCurrentAmount
    SKGError err;
    SKGObjectBase::SKGListSKGObjectBase objects;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_category_display", "", objects))
    accounts.setAttribute("count", SKGServices::intToString(5 + nbAccount + objects.count()));
    int nb = objects.count();
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export categories"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGCategoryObject obj(objects.at(i));
            QDomElement account = doc.createElement("ACCOUNT");
            accounts.appendChild(account);

            account.setAttribute("id", getKmyUniqueIdentifier(obj));
            account.setAttribute("name", obj.getName());
            account.setAttribute("number", "");
            account.setAttribute("type", obj.getCurrentAmount() < 0 ? "13" : "12");

            account.setAttribute("institution", "");

            SKGCategoryObject parentCat;
            obj.getParentCategory(parentCat);

            QString parentId = (parentCat.getID() ? getKmyUniqueIdentifier(parentCat) : (obj.getCurrentAmount() < 0 ? "AStd::Expense" : "AStd::Income"));
            if (parentId == "AStd::Expense") {
                QDomElement subaccount = doc.createElement("SUBACCOUNT");
                accountExpense.appendChild(subaccount);
                subaccount.setAttribute("id", getKmyUniqueIdentifier(obj));
            } else if (parentId == "AStd::Income") {
                QDomElement subaccount = doc.createElement("SUBACCOUNT");
                accountIncome.appendChild(subaccount);
                subaccount.setAttribute("id", getKmyUniqueIdentifier(obj));
            }

            account.setAttribute("parentaccount", parentId);
            account.setAttribute("lastmodified", "");
            account.setAttribute("lastreconciled", "");
            account.setAttribute("opened", "");
            account.setAttribute("currency", stdUnit);
            account.setAttribute("description", "");

            QDomElement subaccounts = doc.createElement("SUBACCOUNTS");
            account.appendChild(subaccounts);

            SKGObjectBase::SKGListSKGObjectBase categories;
            IFOKDO(err, obj.getCategories(categories))
            int nb2 = categories.count();
            for (int j = 0; !err && j < nb2; ++j) {
                QDomElement subaccount = doc.createElement("SUBACCOUNT");
                subaccounts.appendChild(subaccount);

                subaccount.setAttribute("id", getKmyUniqueIdentifier(categories.at(j)));
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
        }

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }
    return err;
}

SKGError SKGImportPluginKmy::exportAccounts(QDomDocument& doc, QDomElement& root, const QString& stdUnit, QDomElement& accounts, QDomElement& accountIncome, QDomElement& accountExpense, int& nbAccounts)
{
    SKGError err;
    accounts = doc.createElement("ACCOUNTS");
    root.appendChild(accounts);

    QDomElement accountAsset;
    {
        QDomElement account = doc.createElement("ACCOUNT");
        accounts.appendChild(account);

        account.setAttribute("id", "AStd::Equity");
        account.setAttribute("name", "Equity");
        account.setAttribute("number", "");
        account.setAttribute("type", "16");
        account.setAttribute("institution", "");
        account.setAttribute("parentaccount", "");
        account.setAttribute("lastmodified", "");
        account.setAttribute("lastreconciled", "");
        account.setAttribute("opened", "");
        account.setAttribute("currency", stdUnit);
        account.setAttribute("description", "");

        QDomElement subaccounts = doc.createElement("SUBACCOUNTS");
        account.appendChild(subaccounts);
    }

    {
        QDomElement account = doc.createElement("ACCOUNT");
        accounts.appendChild(account);

        account.setAttribute("id", "AStd::Asset");
        account.setAttribute("name", "Asset");
        account.setAttribute("number", "");
        account.setAttribute("type", "9");
        account.setAttribute("institution", "");
        account.setAttribute("parentaccount", "");
        account.setAttribute("lastmodified", "");
        account.setAttribute("lastreconciled", "");
        account.setAttribute("opened", "");
        account.setAttribute("currency", stdUnit);
        account.setAttribute("description", "");

        QDomElement subaccounts = doc.createElement("SUBACCOUNTS");
        account.appendChild(subaccounts);
        accountAsset = subaccounts;
    }

    {
        QDomElement account = doc.createElement("ACCOUNT");
        accounts.appendChild(account);

        account.setAttribute("id", "AStd::Liability");
        account.setAttribute("name", "Liability");
        account.setAttribute("number", "");
        account.setAttribute("type", "10");
        account.setAttribute("institution", "");
        account.setAttribute("parentaccount", "");
        account.setAttribute("lastmodified", "");
        account.setAttribute("lastreconciled", "");
        account.setAttribute("opened", "");
        account.setAttribute("currency", stdUnit);
        account.setAttribute("description", "");

        QDomElement subaccounts = doc.createElement("SUBACCOUNTS");
        account.appendChild(subaccounts);
    }

    {
        QDomElement account = doc.createElement("ACCOUNT");
        accounts.appendChild(account);

        account.setAttribute("id", "AStd::Income");
        account.setAttribute("name", "Income");
        account.setAttribute("number", "");
        account.setAttribute("type", "12");
        account.setAttribute("institution", "");
        account.setAttribute("parentaccount", "");
        account.setAttribute("lastmodified", "");
        account.setAttribute("lastreconciled", "");
        account.setAttribute("opened", "");
        account.setAttribute("currency", stdUnit);
        account.setAttribute("description", "");

        QDomElement subaccounts = doc.createElement("SUBACCOUNTS");
        account.appendChild(subaccounts);
        accountIncome = subaccounts;
    }

    {
        QDomElement account = doc.createElement("ACCOUNT");
        accounts.appendChild(account);

        account.setAttribute("id", "AStd::Expense");
        account.setAttribute("name", "Expense");
        account.setAttribute("number", "");
        account.setAttribute("type", "13");
        account.setAttribute("institution", "");
        account.setAttribute("parentaccount", "");
        account.setAttribute("lastmodified", "");
        account.setAttribute("lastreconciled", "");
        account.setAttribute("opened", "");
        account.setAttribute("currency", stdUnit);
        account.setAttribute("description", "");

        QDomElement subaccounts = doc.createElement("SUBACCOUNTS");
        account.appendChild(subaccounts);
        accountExpense = subaccounts;
    }

    SKGObjectBase::SKGListSKGObjectBase objects;
    IFOKDO(err, m_importer->getDocument()->getObjects("v_account", "", objects))
    int nb = objects.count();
    IFOK(err) {
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export accounts"), nb);
        for (int i = 0; !err && i < nb; ++i) {
            SKGAccountObject obj(objects.at(i));
            QDomElement account = doc.createElement("ACCOUNT");
            accounts.appendChild(account);

            account.setAttribute("id", getKmyUniqueIdentifier(obj));
            account.setAttribute("name", obj.getName());
            account.setAttribute("number", obj.getNumber());
            account.setAttribute("type", obj.getType() == SKGAccountObject::CREDITCARD ? "4" : (obj.getType() == SKGAccountObject::INVESTMENT ? "7" : (obj.getType() == SKGAccountObject::ASSETS ? "9" : (obj.getType() == SKGAccountObject::WALLET ? "3" : (obj.getType() == SKGAccountObject::LOAN ? "10" : "1")))));

            SKGBankObject bank;
            err = obj.getBank(bank);
            account.setAttribute("institution", getKmyUniqueIdentifier(bank));

            account.setAttribute("parentaccount", "AStd::Asset");
            account.setAttribute("lastmodified", "");
            account.setAttribute("lastreconciled", "");
            account.setAttribute("opened", "");
            SKGUnitObject unit;
            obj.getUnit(unit);
            QString unitS = SKGUnitObject::getInternationalCode(unit.getName());
            if (unitS.isEmpty()) {
                unitS = "EUR";
            }
            account.setAttribute("currency", unitS);
            account.setAttribute("description", "");

            // Bookmarked account
            QDomElement keyvaluepairs = doc.createElement("KEYVALUEPAIRS");
            account.appendChild(keyvaluepairs);
            if (obj.isBookmarked()) {
                QDomElement pair = doc.createElement("PAIR");
                keyvaluepairs.appendChild(pair);
                pair.setAttribute("key", "PreferredAccount");
                pair.setAttribute("value", "Yes");
            }
            // Closed account
            if (obj.isClosed()) {
                QDomElement pair = doc.createElement("PAIR");
                keyvaluepairs.appendChild(pair);
                pair.setAttribute("key", "mm-closed");
                pair.setAttribute("value", "yes");
            }

            // Maximum and minimum limites
            if (obj.isMaxLimitAmountEnabled()) {
                QDomElement pair = doc.createElement("PAIR");
                keyvaluepairs.appendChild(pair);
                pair.setAttribute("key", "maxCreditAbsolute");
                pair.setAttribute("value", kmyValue(-obj.getMaxLimitAmount()));
            }
            if (obj.isMinLimitAmountEnabled()) {
                QDomElement pair = doc.createElement("PAIR");
                keyvaluepairs.appendChild(pair);
                pair.setAttribute("key", "minBalanceAbsolute");
                pair.setAttribute("value", kmyValue(obj.getMinLimitAmount()));
            }

            // Add it in asset
            QDomElement subaccount = doc.createElement("SUBACCOUNT");
            accountAsset.appendChild(subaccount);
            subaccount.setAttribute("id", getKmyUniqueIdentifier(obj));

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

        SKGENDTRANSACTION(m_importer->getDocument(),  err);
    }

    nbAccounts = nb;
    return err;
}

SKGError SKGImportPluginKmy::exportFile()
{
    // Initialisation
    m_opTreated.clear();

    if (!m_importer) {
        return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters"));
    }
    SKGError err;
    SKGTRACEINFUNCRC(2, err);

    // Open file
    KCompressionDevice file(m_importer->getLocalFileName(false), KCompressionDevice::GZip);
    if (!file.open(QIODevice::WriteOnly)) {
        err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", m_importer->getFileName().toDisplayString()));
    } else {
        QDomDocument doc("KMYMONEY-FILE");
        QDomComment comment = doc.createComment("Generated by libskgbankmodeler");
        doc.appendChild(comment);

        QDomElement root = doc.createElement("KMYMONEY-FILE");
        doc.appendChild(root);
        {
            err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export %1 file", "KMY"), 8);
            IFOK(err) {
                // Step 1-<FILEINFO>
                IFOKDO(err, exportHeader(doc, root));
                IFOKDO(err, m_importer->getDocument()->stepForward(1))

                // // Step 2-<INSTITUTIONS>
                IFOKDO(err, exportInstitutions(doc, root));
                IFOKDO(err, m_importer->getDocument()->stepForward(2))

                // Step 3-<PAYEES>
                IFOKDO(err, exportPayees(doc, root));
                IFOKDO(err, m_importer->getDocument()->stepForward(3))

                // Step 4-<ACCOUNTS>
                // Std accounts
                QString stdUnit = SKGUnitObject::getInternationalCode(m_importer->getDocument()->getPrimaryUnit().Name);
                if (stdUnit.isEmpty()) {
                    stdUnit = "EUR";
                }

                QDomElement accountIncome;
                QDomElement accountExpense;
                QDomElement accounts;

                int nbAccounts = 0;
                IFOKDO(err, exportAccounts(doc, root, stdUnit, accounts, accountIncome, accountExpense, nbAccounts));
                IFOKDO(err, m_importer->getDocument()->stepForward(4))

                // Step 5
                IFOKDO(err, exportCategories(doc, accounts, stdUnit, accountIncome, accountExpense, nbAccounts));
                IFOKDO(err, m_importer->getDocument()->stepForward(5))

                // Step 6-<TRANSACTIONS>
                IFOKDO(err, exportTransactions(doc, root, stdUnit));

                // Step 6-<SCHEDULES>
                IFOKDO(err, exportSchedules(doc, root));
                IFOKDO(err, m_importer->getDocument()->stepForward(6))

                // Step 7-<BUDGETS>
                IFOKDO(err, exportBudgets(doc, root));
                IFOKDO(err, m_importer->getDocument()->stepForward(7))

                // Step 8-<SECURITIES> and <CURRENCIES>
                IFOKDO(err, exportSecurities(doc, root, stdUnit));

                // Save file
                IFOK(err) file.write(doc.toString().toUtf8());
                IFOKDO(err, m_importer->getDocument()->stepForward(8))
            }

            SKGENDTRANSACTION(m_importer->getDocument(),  err);
        }

        file.close();
    }

    // Clean
    m_opTreated.clear();

    return err;
}

SKGError SKGImportPluginKmy::exportOperation(const SKGOperationObject& iOperation, QDomDocument& iDoc, QDomElement& iTransaction)
{
    SKGError err;
    SKGTRACEINFUNCRC(2, err);
    if (!m_opTreated.contains(getKmyUniqueIdentifier(iOperation))) {
        QDomElement transaction = iDoc.createElement("TRANSACTION");
        iTransaction.appendChild(transaction);

        SKGUnitObject unit;
        iOperation.getUnit(unit);

        QString date = iOperation.getAttribute("d_date");
        transaction.setAttribute("id", getKmyUniqueIdentifier(iOperation));
        transaction.setAttribute("entrydate", date);
        transaction.setAttribute("postdate", date);
        transaction.setAttribute("memo", iOperation.getComment());
        transaction.setAttribute("commodity", SKGUnitObject::getInternationalCode(unit.getName()));

        QString reconcileflag = (iOperation.getStatus() == SKGOperationObject::POINTED ? "1" : (iOperation.getStatus() == SKGOperationObject::CHECKED ? "2" : "0"));

        SKGAccountObject act;
        IFOKDO(err, iOperation.getParentAccount(act))

        QDomElement splits = iDoc.createElement("SPLITS");
        transaction.appendChild(splits);

        QDomElement split = iDoc.createElement("SPLIT");
        splits.appendChild(split);

        SKGPayeeObject payeeObject;
        iOperation.getPayee(payeeObject);
        QString payeeId = (payeeObject.getID() ? getKmyUniqueIdentifier(payeeObject) : "");

        int indexSubOp = 1;

        // Split for account
        split.setAttribute("payee", payeeId);
        split.setAttribute("reconciledate", "");
        split.setAttribute("id", "S" % SKGServices::intToString(indexSubOp++).rightJustified(4, '0'));
        double val2 = SKGServices::stringToDouble(iOperation.getAttribute("f_QUANTITY"));
        split.setAttribute("shares", SKGImportPluginKmy::kmyValue(val2));
        split.setAttribute("action", "");
        split.setAttribute("bankid", "");
        split.setAttribute("number", SKGServices::intToString(iOperation.getNumber()));
        split.setAttribute("reconcileflag", reconcileflag);
        split.setAttribute("memo", iOperation.getComment());
        QString originalAmount = iOperation.getProperty("SKG_OP_ORIGINAL_AMOUNT");
        if (!originalAmount.isEmpty()) {
            val2 = qAbs(SKGServices::stringToDouble(originalAmount)) * (val2 / qAbs(val2));
        }
        split.setAttribute("value", SKGImportPluginKmy::kmyValue(val2));
        split.setAttribute("account", getKmyUniqueIdentifier(act));

        SKGOperationObject obj2;
        if (!err && iOperation.isTransfer(obj2)) {
            // It is a transfert
            QString reconcileflag2 = (obj2.getStatus() == SKGOperationObject::POINTED ? "1" : (obj2.getStatus() == SKGOperationObject::CHECKED ? "2" : "0"));

            SKGAccountObject act2;
            IFOKDO(err, obj2.getParentAccount(act2))

            QDomElement split2 = iDoc.createElement("SPLIT");
            splits.appendChild(split2);

            // Split for account
            val2 = -val2;
            split2.setAttribute("payee", payeeId);
            split2.setAttribute("reconciledate", "");
            split2.setAttribute("id", "S" % SKGServices::intToString(indexSubOp++).rightJustified(4, '0'));
            split2.setAttribute("shares", SKGImportPluginKmy::kmyValue(SKGServices::stringToDouble(obj2.getAttribute("f_QUANTITY"))));
            split2.setAttribute("action", "");
            split2.setAttribute("bankid", "");
            split2.setAttribute("number", SKGServices::intToString(obj2.getNumber()));
            split2.setAttribute("reconcileflag", reconcileflag2);
            split2.setAttribute("memo", obj2.getComment());
            split2.setAttribute("value", SKGImportPluginKmy::kmyValue(val2));
            split2.setAttribute("account", getKmyUniqueIdentifier(act2));

            m_opTreated.insert(getKmyUniqueIdentifier(obj2));
        } else {
            SKGObjectBase::SKGListSKGObjectBase subops;
            IFOKDO(err, iOperation.getSubOperations(subops))
            int nb2 = subops.count();
            for (int j = 0; !err && j < nb2; ++j) {
                QDomElement split2 = iDoc.createElement("SPLIT");
                splits.appendChild(split2);

                SKGSubOperationObject subop(subops.at(j));
                SKGCategoryObject cat;
                subop.getCategory(cat);
                split2.setAttribute("payee", payeeId);
                split2.setAttribute("reconciledate", "");
                split2.setAttribute("id", "S" % SKGServices::intToString(indexSubOp++).rightJustified(4, '0'));
                QString shape3 = SKGImportPluginKmy::kmyValue(-subop.getQuantity());
                split2.setAttribute("shares", shape3);
                split2.setAttribute("action", "");
                split2.setAttribute("bankid", "");
                split2.setAttribute("number", SKGServices::intToString(iOperation.getNumber()));
                split2.setAttribute("reconcileflag", reconcileflag);
                split2.setAttribute("memo", subop.getComment());
                split2.setAttribute("value", shape3);
                split2.setAttribute("account", date == "0000-00-00" ? "AStd::Equity" : (cat.getID() ? getKmyUniqueIdentifier(cat) : ""));
            }
        }

        m_opTreated.insert(getKmyUniqueIdentifier(iOperation));
    }
    return err;
}

QString SKGImportPluginKmy::kmyValue(double iValue)
{
    QString output;
    for (int i = 0; output.isEmpty() && i < 11; ++i) {
        QString d = SKGServices::doubleToString(pow(10, i) * iValue);
        if (d.indexOf('.') == -1) {
            output = d % '/' % SKGServices::intToString(qPow(10, i));
        }
    }
    return output;
}

double SKGImportPluginKmy::toKmyValue(const QString& iString)
{
    double output = 0;
    QStringList vals = SKGServices::splitCSVLine(iString, '/');
    if (vals.count() == 1) {
        output = SKGServices::stringToDouble(vals.at(0));
    } else if (vals.count() == 2) {
        output = SKGServices::stringToDouble(vals.at(0)) / SKGServices::stringToDouble(vals.at(1));
    }
    return output;
}

QString SKGImportPluginKmy::getKmyUniqueIdentifier(const SKGObjectBase& iObject)
{
    QString id;
    if (iObject.getID() != 0) {
        QString table = iObject.getRealTable();
        if (table == "operation" || table == "suboperation") {
            // T000000000000003623
            id = 'T' % SKGServices::intToString(iObject.getID()).rightJustified(18, '0');
        } else if (table == "payee") {
            // P000030
            id = 'P' % SKGServices::intToString(iObject.getID()).rightJustified(6, '0');
        } else {
            id = iObject.getUniqueID();
        }
    }
    return id;
}

QString SKGImportPluginKmy::getMimeTypeFilter() const
{
    return "*.kmy|" % i18nc("A file format", "KMyMoney document");
}

#include <skgimportpluginkmy.moc>
