/***************************************************************************
 *   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 implements classes SKGAccountObject.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgaccountobject.h"

#include <KLocale>

#include "skgbankobject.h"
#include "skgoperationobject.h"
#include "skgsuboperationobject.h"
#include "skgunitobject.h"
#include "skginterestobject.h"
#include "skgpayeeobject.h"
#include "skgdocumentbank.h"
#include "skgtraces.h"

SKGAccountObject::SKGAccountObject(SKGDocument* iDocument, int iID) : SKGNamedObject(iDocument, "v_account", iID) {}

SKGAccountObject::~SKGAccountObject() {}

SKGAccountObject::SKGAccountObject(const SKGAccountObject& iObject)
    : SKGNamedObject(iObject)
{}

SKGAccountObject::SKGAccountObject(const SKGNamedObject& iObject)
    : SKGNamedObject(iObject.getDocument(), "v_account", iObject.getID())
{
    if (iObject.getRealTable() == "account") {
        copyFrom(iObject);
    } else {
        *this = SKGNamedObject(iObject.getDocument(), "v_account", iObject.getID());
    }
}

SKGAccountObject::SKGAccountObject(const SKGObjectBase& iObject)
{
    if (iObject.getRealTable() == "account") {
        copyFrom(iObject);
    } else {
        *this = SKGNamedObject(iObject.getDocument(), "v_account", iObject.getID());
    }
}

const SKGAccountObject& SKGAccountObject::operator= (const SKGObjectBase& iObject)
{
    copyFrom(iObject);
    return *this;
}

SKGError SKGAccountObject::setInitialBalance(double iBalance, const SKGUnitObject& iUnit)
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    if (getDocument()) {
        // Delete previous initial balance for this account
        err = getDocument()->executeSqliteOrder("DELETE FROM operation  WHERE d_date='0000-00-00' AND rd_account_id=" % SKGServices::intToString(getID()));

        // Creation of new initial balance
        IFOK(err) {
            SKGOperationObject initialBalanceOp;
            err = addOperation(initialBalanceOp, true);
            IFOKDO(err, initialBalanceOp.setAttribute("d_date", "0000-00-00"))
            IFOKDO(err, initialBalanceOp.setUnit(iUnit))
            IFOKDO(err, initialBalanceOp.setStatus(SKGOperationObject::CHECKED))
            IFOKDO(err, initialBalanceOp.save())

            SKGSubOperationObject initialBalanceSubOp;
            IFOKDO(err, initialBalanceOp.addSubOperation(initialBalanceSubOp))
            IFOKDO(err, initialBalanceSubOp.setAttribute("d_date", "0000-00-00"))
            IFOKDO(err, initialBalanceSubOp.setQuantity(iBalance))
            IFOKDO(err, initialBalanceSubOp.save())
        }
    }
    return err;
}

SKGError SKGAccountObject::getInitialBalance(double& oBalance, SKGUnitObject& oUnit)
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    // Initialisation
    oBalance = 0;
    oUnit = SKGUnitObject();
    QString unitName = static_cast<SKGDocumentBank*>(getDocument())->getPrimaryUnit().Symbol;

    // Get initial balance
    SKGStringListList listTmp;
    err = getDocument()->executeSelectSqliteOrder("SELECT f_QUANTITY, t_UNIT FROM  v_operation_tmp1  WHERE d_date='0000-00-00' AND rd_account_id=" % SKGServices::intToString(getID()), listTmp);
    if (!err && listTmp.count() > 1) {
        oBalance = SKGServices::stringToDouble(listTmp.at(1).at(0));
        unitName = listTmp.at(1).at(1);

        oUnit = SKGUnitObject(getDocument());
        err = oUnit.setSymbol(unitName);
        IFOKDO(err, oUnit.load())
    }
    return err;
}

SKGError SKGAccountObject::setBank(const SKGBankObject& iBank)
{
    return setAttribute("rd_bank_id", SKGServices::intToString(iBank.getID()));
}

SKGError SKGAccountObject::getBank(SKGBankObject& oBank) const
{
    SKGError err = getDocument()->getObject("v_bank", "id=" % getAttribute("rd_bank_id"), oBank);
    return err;
}

SKGError SKGAccountObject::setLinkedAccount(const SKGAccountObject& iAccount)
{
    return setAttribute("r_account_id", SKGServices::intToString(iAccount.getID()));
}

SKGError SKGAccountObject::getLinkedAccount(SKGAccountObject& oAccount) const
{
    SKGError err = getDocument()->getObject("v_account", "id=" % getAttribute("r_account_id"), oAccount);
    return err;
}

SKGError SKGAccountObject::getLinkedByAccounts(SKGListSKGObjectBase& oAccounts) const
{
    SKGError err;
    if (getDocument()) {
        err = getDocument()->getObjects("v_account",
                                        "r_account_id=" % SKGServices::intToString(getID()) ,
                                        oAccounts);
    }
    return err;
}

SKGError SKGAccountObject::setNumber(const QString& iNumber)
{
    return setAttribute("t_number", iNumber);
}

QString SKGAccountObject::getNumber() const
{
    return getAttribute("t_number");
}

SKGError SKGAccountObject::setComment(const QString& iComment)
{
    return setAttribute("t_comment", iComment);
}

QString SKGAccountObject::getComment() const
{
    return getAttribute("t_comment");
}

SKGError SKGAccountObject::setAgencyNumber(const QString& iNumber)
{
    return setAttribute("t_agency_number", iNumber);
}

QString SKGAccountObject::getAgencyNumber() const
{
    return getAttribute("t_agency_number");
}

SKGError SKGAccountObject::setAgencyAddress(const QString& iAddress)
{
    return setAttribute("t_agency_address", iAddress);
}

QString SKGAccountObject::getAgencyAddress() const
{
    return getAttribute("t_agency_address");
}

SKGError SKGAccountObject::addOperation(SKGOperationObject& oOperation, bool iForce)
{
    SKGError err;
    if (getID() == 0) {
        err = SKGError(ERR_FAIL, i18nc("Error message", "%1 failed because linked object is not yet saved in the database.", QString("SKGAccountObject::addOperation")));
    } else {
        oOperation = SKGOperationObject(getDocument());
        err = oOperation.setParentAccount(*this, iForce);
    }
    return err;
}

int SKGAccountObject::getNbOperation() const
{
    int nb = 0;
    if (getDocument()) {
        getDocument()->getNbObjects("operation", "rd_account_id=" % SKGServices::intToString(getID()), nb);
    }
    return nb;
}

SKGError SKGAccountObject::getOperations(SKGListSKGObjectBase& oOperations) const
{
    SKGError err;
    if (getDocument()) {
        err = getDocument()->getObjects("v_operation",
                                        "rd_account_id=" % SKGServices::intToString(getID()) ,
                                        oOperations);
    }
    return err;
}

double SKGAccountObject::getCurrentAmount() const
{
    return SKGServices::stringToDouble(getAttributeFromView("v_account_amount", "f_CURRENTAMOUNT"));
}

double SKGAccountObject::getAmount(const QDate& iDate, bool iOnlyCurrencies) const
{
    SKGTRACEINFUNC(10);
    double output = 0;
    if (getDocument()) {
        // Search result in cache
        QString ids = SKGServices::intToString(getID());
        QString dates = SKGServices::dateToSqlString(QDateTime(iDate));
        QString key = "getamount-" % ids % '-' % dates;
        QString val = getDocument()->getCachedValue(key);
        if (val.isEmpty()) {
            SKGStringListList listTmp;
            SKGError err = getDocument()->executeSelectSqliteOrder("SELECT TOTAL(f_QUANTITY), rc_unit_id FROM v_operation_tmp1  WHERE "
                           "d_date<='" % dates % "' AND t_template='N' AND rd_account_id=" % ids %
                           (iOnlyCurrencies ? " AND t_TYPEUNIT IN ('1', '2', 'C')" : "") %
                           " GROUP BY rc_unit_id",
                           listTmp);
            int nb = listTmp.count();
            for (int i = 1; !err && i < nb ; ++i) {
                QString quantity = listTmp.at(i).at(0);
                QString unitid = listTmp.at(i).at(1);

                double coef = 1;
                QString val2 = getDocument()->getCachedValue("unitvalue-" % unitid);
                if (!val2.isEmpty()) {
                    // Yes
                    coef = SKGServices::stringToDouble(val2);
                } else {
                    // No
                    SKGUnitObject unit(getDocument(), SKGServices::stringToInt(unitid));
                    if (unit.getType() != SKGUnitObject::PRIMARY) {
                        coef = unit.getAmount(iDate);
                    }
                }

                output += coef * SKGServices::stringToDouble(quantity);
            }
            (static_cast<SKGDocument*>(getDocument()))->addValueInCache(key, SKGServices::doubleToString(output));
        } else {
            output = SKGServices::stringToDouble(val);
        }
    }
    return output;
}

SKGError SKGAccountObject::setType(SKGAccountObject::AccountType iType)
{
    return setAttribute("t_type", (iType == CURRENT ? "C" :
                                   (iType == CREDITCARD ? "D" :
                                    (iType == ASSETS ? "A" :
                                     (iType == INVESTMENT ? "I" :
                                      (iType == WALLET ? "W" :
                                       (iType == PENSION ? "P" :
                                        (iType == LOAN ? "L" :
                                         (iType == SAVING ? "S" :
                                          "O")))))))));
}

SKGAccountObject::AccountType SKGAccountObject::getType() const
{
    QString typeString = getAttribute("t_type");
    return (typeString == "C" ? CURRENT :
            (typeString == "D" ? CREDITCARD :
             (typeString == "A" ? ASSETS :
              (typeString == "I" ? INVESTMENT :
               (typeString == "W" ? WALLET :
                (typeString == "P" ? PENSION :
                 (typeString == "L" ? LOAN :
                  (typeString == "S" ? SAVING : OTHER))))))));
}

SKGError SKGAccountObject::setClosed(bool iClosed)
{
    return setAttribute("t_close", iClosed ? "Y" : "N");
}

bool SKGAccountObject::isClosed() const
{
    return (getAttribute("t_close") == "Y" ? true : false);
}

SKGError SKGAccountObject::bookmark(bool iBookmark)
{
    return setAttribute("t_bookmarked", iBookmark ? "Y" : "N");
}

bool SKGAccountObject::isBookmarked() const
{
    return (getAttribute("t_bookmarked") == "Y" ? true : false);
}

SKGError SKGAccountObject::maxLimitAmountEnabled(bool iEnabled)
{
    return setAttribute("t_maxamount_enabled", iEnabled ? "Y" : "N");
}

bool SKGAccountObject::isMaxLimitAmountEnabled() const
{
    return (getAttribute("t_maxamount_enabled") == "Y" ? true : false);
}

SKGError SKGAccountObject::setMaxLimitAmount(double iAmount)
{
    SKGError err = setAttribute("f_maxamount", SKGServices::doubleToString(iAmount));
    if (!err && getMinLimitAmount() > iAmount) {
        err = setMinLimitAmount(iAmount);
    }
    return err;
}

double SKGAccountObject::getMaxLimitAmount() const
{
    return SKGServices::stringToDouble(getAttribute("f_maxamount"));
}

SKGError SKGAccountObject::minLimitAmountEnabled(bool iEnabled)
{
    return setAttribute("t_minamount_enabled", iEnabled ? "Y" : "N");
}

bool SKGAccountObject::isMinLimitAmountEnabled() const
{
    return (getAttribute("t_minamount_enabled") == "Y" ? true : false);
}

SKGError SKGAccountObject::setMinLimitAmount(double iAmount)
{
    SKGError err = setAttribute("f_minamount", SKGServices::doubleToString(iAmount));
    if (!err && getMaxLimitAmount() < iAmount) {
        err = setMaxLimitAmount(iAmount);
    }
    return err;
}

double SKGAccountObject::getMinLimitAmount() const
{
    return SKGServices::stringToDouble(getAttribute("f_minamount"));
}

SKGError SKGAccountObject::setReconciliationDate(const QDate& iDate)
{
    return setAttribute("d_reconciliationdate", SKGServices::dateToSqlString(QDateTime(iDate)));
}

QDate SKGAccountObject::getReconciliationDate() const
{
    return SKGServices::stringToTime(getAttribute("d_reconciliationdate")).date();
}

SKGError SKGAccountObject::getUnit(SKGUnitObject& oUnit) const
{
    // Get initial amount
    SKGStringListList listTmp;
    SKGError err = getDocument()->executeSelectSqliteOrder("SELECT t_UNIT FROM  v_suboperation_consolidated  WHERE d_date='0000-00-00' AND rd_account_id=" % SKGServices::intToString(getID()), listTmp);
    IFOK(err) {
        // Is initial amount existing ?
        if (listTmp.count() > 1) {
            // Yes ==> then the amount is the amount of the initial value
            oUnit = SKGUnitObject(getDocument());
            err = oUnit.setSymbol(listTmp.at(1).at(0));
            IFOKDO(err, oUnit.load())
        } else {
            // No ==> we get the preferred unit
            SKGObjectBase::SKGListSKGObjectBase units;
            err = getDocument()->getObjects("v_unit",
                                            "t_type IN ('1', '2', 'C') AND EXISTS(SELECT 1 FROM operation WHERE rc_unit_id=v_unit.id AND rd_account_id=" % SKGServices::intToString(getID()) % ") ORDER BY t_type", units);
            int nb = units.count();
            if (nb) {
                oUnit = units.at(0);
            }
        }
    }
    return err;
}

SKGError SKGAccountObject::addInterest(SKGInterestObject& oInterest)
{
    SKGError err;
    if (getID() == 0) {
        err = SKGError(ERR_FAIL, i18nc("Error message", "%1 failed because linked object is not yet saved in the database.", QString("SKGAccountObject::addInterest")));
    } else {
        oInterest = SKGInterestObject(static_cast<SKGDocumentBank*>(getDocument()));
        err = oInterest.setAccount(*this);
    }
    return err;
}

SKGError SKGAccountObject::getInterests(SKGListSKGObjectBase& oInterestList) const
{
    SKGError err = getDocument()->getObjects("v_interest",
                   "rd_account_id=" % SKGServices::intToString(getID()),
                   oInterestList);
    return err;
}

SKGError SKGAccountObject::getInterest(const QDate& iDate, SKGInterestObject& oInterest) const
{
    QString ids = SKGServices::intToString(getID());
    QString dates = SKGServices::dateToSqlString(QDateTime(iDate));
    SKGError err = SKGObjectBase::getDocument()->getObject("v_interest",
                   "rd_account_id=" % ids % " AND d_date<='" % dates %
                   "' AND  ABS(strftime('%s','" % dates %
                   "')-strftime('%s',d_date))=(SELECT MIN(ABS(strftime('%s','" % dates %
                   "')-strftime('%s',u2.d_date))) FROM interest u2 WHERE u2.rd_account_id=" % ids %
                   " AND u2.d_date<='" % dates % "')",
                   oInterest);

    // If not found then get first
    IFKO(err) err = SKGObjectBase::getDocument()->getObject("v_interest",
                    "rd_account_id=" % SKGServices::intToString(getID()) % " AND d_date=(SELECT MIN(d_date) FROM interest WHERE rd_account_id=" %
                    SKGServices::intToString(getID()) % ')',
                    oInterest);
    return err;
}

SKGError SKGAccountObject::getInterestItems(QList<SKGAccountObject::SKGInterestItem>& oInterestList, double& oInterests, int iYear) const
{
    oInterestList.clear();
    SKGError err;

    // Initial date
    int y = iYear;
    if (y == 0) {
        y = QDate::currentDate().year();
    }
    QDate initialDate = QDate(y, 1, 1);
    QDate lastDate = QDate(y, 12, 31);

    oInterests = 0;
    bool computationNeeded = false;

    // Add operations
    SKGObjectBase::SKGListSKGObjectBase items;
    err = getDocument()->getObjects("v_operation", "rd_account_id=" % SKGServices::intToString(getID()) %
                                    " AND t_template='N' AND t_TYPEUNIT IN ('1', '2', 'C')"
                                    " AND d_date>='" % SKGServices::dateToSqlString(QDateTime(initialDate)) % "' "
                                    " AND d_date<='" % SKGServices::dateToSqlString(QDateTime(lastDate)) % "' ORDER BY d_date", items);
    int nb = items.count();
    for (int i = 0; !err && i < nb; ++i) {
        SKGOperationObject ob(items.at(i));

        SKGInterestItem itemI;
        itemI.object = ob;
        itemI.date = ob.getDate();
        itemI.valueDate = itemI.date;
        itemI.rate = 0;
        itemI.base = 0;
        itemI.coef = 0;
        itemI.annualInterest = 0;
        itemI.accruedInterest = 0;
        itemI.amount = ob.getCurrentAmount();

        oInterestList.push_back(itemI);
    }

    // Add interest
    IFOK(err) {
        err = getDocument()->getObjects("v_interest", "rd_account_id=" % SKGServices::intToString(getID()) %
                                        " AND d_date>='" % SKGServices::dateToSqlString(QDateTime(initialDate)) % "' "
                                        " AND d_date<='" % SKGServices::dateToSqlString(QDateTime(lastDate)) % "' ORDER BY d_date", items);

        int pos = 0;
        int nb2 = items.count();
        for (int i = 0; !err && i < nb2; ++i) {
            SKGInterestObject ob(items.at(i));

            SKGInterestItem itemI;
            itemI.object = ob;
            itemI.date = ob.getDate();
            itemI.valueDate = itemI.date;
            itemI.rate = ob.getRate();
            itemI.base = SKGServices::stringToInt(ob.getAttribute("t_base"));
            itemI.coef = 0;
            itemI.annualInterest = 0;
            itemI.accruedInterest = 0;
            itemI.amount = 0;

            int nb3 = oInterestList.count();
            for (int j = pos; !err && j < nb3; ++j) {
                if (itemI.date <= oInterestList.at(j).date) {
                    break;
                } else {
                    ++pos;
                }
            }

            oInterestList.insert(pos, itemI);
            computationNeeded = true;
        }
    }

    // Get first interest
    IFOK(err) {
        SKGInterestObject firstInterest;
        if (getInterest(initialDate, firstInterest).isSucceeded()) {
            if (firstInterest.getDate() < initialDate) {
                SKGInterestItem itemI;
                itemI.object = firstInterest;
                itemI.date = initialDate;
                itemI.valueDate = initialDate;
                itemI.rate = firstInterest.getRate();
                itemI.base = 0;
                itemI.coef = 0;
                itemI.annualInterest = 0;
                itemI.accruedInterest = 0;
                itemI.amount = 0;

                oInterestList.insert(0, itemI);
                computationNeeded = true;
            }
        }
    }

    // Launch computation
    IFOK(err) {
        if (computationNeeded) {
            err = computeInterestItems(oInterestList, oInterests, y);
        } else {
            // Drop temporary table
            IFOKDO(err, getDocument()->executeSqliteOrder("DROP TABLE IF EXISTS interest_result"))
            // Create fake table
            IFOKDO(err, getDocument()->executeSqliteOrder("CREATE TEMP TABLE interest_result(a)"))
        }
    }
    return err;
}

SKGError SKGAccountObject::computeInterestItems(QList<SKGAccountObject::SKGInterestItem>& ioInterestList, double& oInterests, int iYear) const
{
    SKGError err;

    // Sum annual interest
    oInterests = 0;

    // Initial date
    int y = iYear;
    if (y == 0) {
        y = QDate::currentDate().year();
    }
    QDate initialDate = QDate(y, 1, 1);

    // Default interest item
    SKGInterestItem currentInterest;
    currentInterest.date = initialDate;
    currentInterest.valueDate = currentInterest.date;
    currentInterest.rate = 0;
    currentInterest.coef = 0;
    currentInterest.annualInterest = 0;
    currentInterest.accruedInterest = 0;

    int nb = ioInterestList.count();
    for (int i = 0; !err && i < nb; ++i) {
        SKGInterestItem tmp = ioInterestList.at(i);
        SKGObjectBase object = tmp.object;
        if (object.getRealTable() == "operation") {
            // Get operations
            SKGOperationObject op(object);

            // Get current amount
            tmp.amount = op.getCurrentAmount();

            // Get value date computation mode
            SKGInterestObject::ValueDateMode valueMode = SKGInterestObject::FIFTEEN;
            SKGInterestObject::InterestMode baseMode = SKGInterestObject::FIFTEEN24;
            if (currentInterest.object.getRealTable() == "interest") {
                SKGInterestObject interestObj(currentInterest.object);
                valueMode = (tmp.amount >= 0 ? interestObj.getIncomeValueDateMode() : interestObj.getExpenditueValueDateMode());
                baseMode = interestObj.getInterestComputationMode();

                tmp.rate = interestObj.getRate();
            }

            // Compute value date
            if (object.getRealTable() == "operation") {
                if (valueMode == SKGInterestObject::FIFTEEN) {
                    if (tmp.amount >= 0) {
                        if (tmp.date.day() <= 15) {
                            tmp.valueDate = tmp.date.addDays(16 - tmp.date.day());
                        } else {
                            tmp.valueDate = tmp.date.addMonths(1).addDays(1 - tmp.date.day());
                        }
                    } else {
                        if (tmp.date.day() <= 15) {
                            tmp.valueDate = tmp.date.addDays(1 - tmp.date.day());
                        } else {
                            tmp.valueDate = tmp.date.addDays(16 - tmp.date.day());
                        }
                    }
                } else {
                    tmp.valueDate = tmp.date.addDays(tmp.amount >= 0 ? (static_cast<int>(valueMode)) - 1 : - (static_cast<int>(valueMode)) + 1);
                }
            }

            // Compute coef
            if (baseMode == SKGInterestObject::DAYS365) {
                QDate last(tmp.date.year(), 12, 31);
                tmp.coef = tmp.valueDate.daysTo(last) + 1;
                tmp.coef /= 365;
            } else if (baseMode == SKGInterestObject::DAYS360) {
                QDate last(tmp.date.year(), 12, 31);
                tmp.coef = 360 * (last.year() - tmp.valueDate.year()) + 30 * (last.month() - tmp.valueDate.month()) + (last.day() - tmp.valueDate.day());  // TODO(Stephane MANKOWSKI) must be confirmed
                tmp.coef /= 360;
            } else {
                tmp.coef = 2 * (12 - tmp.valueDate.month()) + (tmp.valueDate.day() <= 15 ? 2 : 1);
                tmp.coef /= 24;
            }
            if (tmp.valueDate.year() != iYear) {
                tmp.coef = 0;
            }

            // Compute annual interest
            tmp.annualInterest = tmp.amount * tmp.coef * tmp.rate / 100;

        } else if (object.getRealTable() == "interest") {
            // Compute coef
            if (tmp.base == 365) {
                QDate last(tmp.date.year(), 12, 31);
                tmp.coef = tmp.valueDate.daysTo(last) + 1;
                tmp.coef /= 365;
            } else if (tmp.base == 360) {
                QDate last(tmp.date.year(), 12, 31);
                tmp.coef = 360 * (last.year() - tmp.valueDate.year()) + 30 * (last.month() - tmp.valueDate.month()) + (last.day() - tmp.valueDate.day());  // TODO(Stephane MANKOWSKI) must be confirmed
                tmp.coef /= 360;
            } else {
                tmp.coef = 2 * (12 - tmp.valueDate.month()) + (tmp.valueDate.day() <= 15 ? 2 : 1);
                tmp.coef /= 24;
            }
            if (tmp.valueDate.year() != iYear) {
                tmp.coef = 0;
            }

            // Compute annual interest
            // BUG 329568: We must ignore operations of the day
            tmp.amount = getAmount(tmp.valueDate.addDays(-1), true);
            tmp.annualInterest = tmp.amount * tmp.coef * (tmp.rate - currentInterest.rate) / 100;

            currentInterest = tmp;
        }

        // Compute sum
        oInterests += tmp.annualInterest;

        // Compute accrued interest
        tmp.accruedInterest = oInterests - getAmount(tmp.date, true) * tmp.coef * tmp.rate / 100;

        ioInterestList[i] = tmp;
    }

    // Create temporary table
    IFOK(err) {
        QStringList sqlOrders;
        sqlOrders << "DROP TABLE IF EXISTS interest_result"
                  << "CREATE TEMP TABLE interest_result("
                  "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
                  "d_date DATE NOT NULL,"
                  "d_valuedate DATE NOT NULL,"
                  "t_comment TEXT NOT NULL DEFAULT '',"
                  "f_currentamount FLOAT NOT NULL DEFAULT 0,"
                  "f_coef FLOAT NOT NULL DEFAULT 0,"
                  "f_rate FLOAT NOT NULL DEFAULT 0,"
                  "f_annual_interest FLOAT NOT NULL DEFAULT 0,"
                  "f_accrued_interest FLOAT NOT NULL DEFAULT 0"
                  ")";
        err = getDocument()->executeSqliteOrders(sqlOrders);

        // Fill table
        int nb2 = ioInterestList.count();
        for (int i = 0; !err && i < nb2; ++i) {
            SKGInterestItem interest = ioInterestList.at(i);
            SKGObjectBase object = interest.object;
            QString sqlinsert =
                "INSERT INTO interest_result (d_date,d_valuedate,t_comment,f_currentamount,f_coef,f_rate,f_annual_interest,f_accrued_interest) "
                " VALUES ('" % SKGServices::dateToSqlString(QDateTime(interest.date)) %
                "','" % SKGServices::dateToSqlString(QDateTime(interest.valueDate)) %
                "','" % SKGServices::stringToSqlString(object.getRealTable() == "operation" ? i18nc("Noun", "Relative to operation '%1'", SKGOperationObject(object).getDisplayName()) : i18nc("Noun", "Rate change")) %
                "'," % SKGServices::doubleToString(interest.amount) %
                ',' % SKGServices::doubleToString(interest.coef) %
                ',' % SKGServices::doubleToString(interest.rate) %
                ',' % SKGServices::doubleToString(interest.annualInterest) %
                ',' % SKGServices::doubleToString(interest.accruedInterest) %
                ")";

            err = getDocument()->executeSqliteOrder(sqlinsert);
        }
    }
    return err;
}

SKGError SKGAccountObject::transferDeferredOperations(const SKGAccountObject& iTargetAccount, const QDate& iDate)
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    //
    SKGDocumentBank* doc = qobject_cast<SKGDocumentBank*>(getDocument());
    if (doc) {
        // Get pointed operations
        SKGObjectBase::SKGListSKGObjectBase operations;
        IFOKDO(err, getDocument()->getObjects("v_operation", "rd_account_id=" % SKGServices::intToString(getID()) % " AND t_status='P'", operations))
        int nb = operations.count();
        if (nb) {
            SKGOperationObject mergedOperations;
            SKGOperationObject balancedOperations;
            for (int i = 0; !err && i < nb; ++i) {
                SKGOperationObject op(operations.at(i));

                // Create the balance operation
                SKGOperationObject opdup;
                IFOKDO(err, op.duplicate(opdup, iDate))

                SKGListSKGObjectBase subops;
                IFOKDO(err, opdup.getSubOperations(subops))
                int nbsupops = subops.count();
                for (int j = 0; !err && j < nbsupops; ++j) {
                    SKGSubOperationObject subop(subops.at(j));
                    IFOKDO(err, subop.setDate(op.getDate()))
                    IFOKDO(err, subop.setQuantity(-subop.getQuantity()))
                    IFOKDO(err, subop.save())
                }

                if (i == 0) {
                    mergedOperations = opdup;
                } else {
                    IFOKDO(err, mergedOperations.mergeSuboperations(opdup))
                }

                // Create the duplicate in target account
                SKGOperationObject opduptarget;
                IFOKDO(err, op.duplicate(opduptarget))
                IFOKDO(err, opduptarget.setDate(op.getDate()))
                IFOKDO(err, opduptarget.setParentAccount(iTargetAccount))
                IFOKDO(err, opduptarget.setImported(op.isImported()))
                IFOKDO(err, opduptarget.setImportID(op.getImportID()))
                IFOKDO(err, opduptarget.setGroupOperation(mergedOperations))
                IFOKDO(err, opduptarget.setStatus(SKGOperationObject::POINTED))
                IFOKDO(err, opduptarget.save())
                IFOKDO(err, mergedOperations.load())  // To reload the modif done by the setGroupOperation

                // Check the operation
                IFOKDO(err, op.setStatus(SKGOperationObject::CHECKED))
                IFOKDO(err, op.save())
            }

            // Check the balance operation
            IFOKDO(err, mergedOperations.setPayee(SKGPayeeObject()))
            IFOKDO(err, mergedOperations.setStatus(SKGOperationObject::CHECKED))
            IFOKDO(err, mergedOperations.save())
        }
    }

    return err;
}

SKGError SKGAccountObject::autoReconcile(double iBalance)
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    //
    SKGDocumentBank* doc = qobject_cast<SKGDocumentBank*>(getDocument());
    if (doc) {
        // Get unit
        SKGServices::SKGUnitInfo unit1 = doc->getPrimaryUnit();
        SKGUnitObject unitAccount;
        if (getUnit(unitAccount).isSucceeded()) {
            if (!unitAccount.getSymbol().isEmpty()) {
                unit1.Symbol = unitAccount.getSymbol();
                unit1.Value = SKGServices::stringToDouble(unitAccount.getAttribute("f_CURRENTAMOUNT"));
            }
        }

        // Get balance of checked operations
        double balance = 0;
        SKGStringListList listTmp2;
        err = getDocument()->executeSelectSqliteOrder(
                  "SELECT TOTAL(f_CHECKED) from v_account_display WHERE id=" % SKGServices::intToString(getID()),
                  listTmp2);
        if (listTmp2.count() == 2) {
            balance = SKGServices::stringToDouble(listTmp2.at(1).at(0)) / unit1.Value;
        }


        // Get all imported operation
        double diff = balance - iBalance;
        SKGObjectBase::SKGListSKGObjectBase operations;
        IFOKDO(err, getDocument()->getObjects("v_operation", "rd_account_id=" % SKGServices::intToString(getID()) % " AND t_status='N' AND t_template='N' AND t_imported IN ('Y','P') ORDER BY d_date, id", operations))


        // Comparison
        QString zero = doc->formatMoney(0, unit1);
        QString negativezero = doc->formatMoney(-EPSILON, unit1);

        // Try to find the soluce
        int nb = operations.count();

        QString sdiff = doc->formatMoney(diff, unit1);
        int limit = -1;
        bool moreThanOneSoluceFound = false;
        for (int i = 0; !moreThanOneSoluceFound && !err && i < nb; ++i) {
            SKGOperationObject op(operations.at(i));
            double amount = op.getCurrentAmount();
            diff += amount;
            sdiff = doc->formatMoney(diff, unit1);
            SKGTRACEL(5) << (i + 1) << "/" << nb << ": Amount=" << amount << " / New diff=" << sdiff << endl;
            if (sdiff == zero || sdiff == negativezero) {
                if (limit != -1) {
                    err = getDocument()->sendMessage(i18nc("An information message",  "More than one solution is possible for this auto reconciliation."));
                    moreThanOneSoluceFound = true;
                } else {
                    limit = i;
                }
            }
        }

        // Check all
        if (limit != -1) {
            for (int i = 0; !err && i <= limit; ++i) {
                SKGOperationObject op(operations.at(i));
                err = op.setStatus(SKGOperationObject::POINTED);
                IFOKDO(err, op.save(true, false))
            }
        } else {
            err = SKGError(ERR_FAIL, i18nc("Error message",  "Can not find the imported operations for obtaining the expected final balance"));
        }
    }

    return err;
}

SKGError SKGAccountObject::merge(const SKGAccountObject& iAccount)
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    // Get initial balances
    double balance1 = 0.0;
    SKGUnitObject unit1;
    err = getInitialBalance(balance1, unit1);

    double balance2 = 0.0;
    SKGUnitObject unit2;
    IFOKDO(err, const_cast<SKGAccountObject*>(&iAccount)->getInitialBalance(balance2, unit2))

    // Transfert operations
    SKGObjectBase::SKGListSKGObjectBase ops;
    IFOKDO(err, iAccount.getOperations(ops))
    int nb = ops.count();
    for (int i = 0; !err && i < nb; ++i) {
        SKGOperationObject op(ops.at(i));
        err = op.setParentAccount(*this);
        IFOKDO(err, op.save(true, false))
    }

    // Set initial balance
    SKGUnitObject unit = unit1;
    if (!unit1.exist()) {
        unit = unit2;
    }
    if (unit.exist()) {
        double balance = balance1 + SKGUnitObject::convert(balance2, unit2, unit);
        IFOKDO(err, setInitialBalance(balance, unit))
    }
    // Remove account
    IFOKDO(err, iAccount.remove())
    return err;
}

#include "skgaccountobject.moc"
