summaryrefslogtreecommitdiffstats
path: root/kmymoney2/mymoney/mymoneyfile.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-07-04 22:38:03 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2011-07-04 22:38:03 +0000
commitdadc34655c3ab961b0b0b94a10eaaba710f0b5e8 (patch)
tree99e72842fe687baea16376a147619b6048d7e441 /kmymoney2/mymoney/mymoneyfile.cpp
downloadkmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.tar.gz
kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.zip
Added kmymoney
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kmymoney2/mymoney/mymoneyfile.cpp')
-rw-r--r--kmymoney2/mymoney/mymoneyfile.cpp2332
1 files changed, 2332 insertions, 0 deletions
diff --git a/kmymoney2/mymoney/mymoneyfile.cpp b/kmymoney2/mymoney/mymoneyfile.cpp
new file mode 100644
index 0000000..3245746
--- /dev/null
+++ b/kmymoney2/mymoney/mymoneyfile.cpp
@@ -0,0 +1,2332 @@
+/***************************************************************************
+ mymoneyfile.cpp
+ -------------------
+ copyright : (C) 2000 by Michael Edwardes
+ (C) 2002, 2007-2008 by Thomas Baumgart
+ email : mte@users.sourceforge.net
+ ipwizard@users.sourceforge.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+// ----------------------------------------------------------------------------
+// QT Includes
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+
+// ----------------------------------------------------------------------------
+// KDE Includes
+
+#include <kdebug.h>
+#include <klocale.h>
+
+// ----------------------------------------------------------------------------
+// Project Includes
+#include "storage/mymoneyseqaccessmgr.h"
+#include "mymoneyfile.h"
+#include "mymoneyreport.h"
+#include "mymoneybudget.h"
+#include "mymoneyprice.h"
+#include "mymoneyobjectcontainer.h"
+
+#ifndef HAVE_CONFIG_H
+#define VERSION "UNKNOWN"
+#else
+#include "config.h"
+#endif
+
+const QString MyMoneyFile::OpeningBalancesPrefix = I18N_NOOP("Opening Balances");
+const QString MyMoneyFile::AccountSeperator = ":";
+
+// include the following line to get a 'cout' for debug purposes
+// #include <iostream>
+MyMoneyFile* MyMoneyFile::_instance = 0;
+
+class MyMoneyFile::Private
+{
+public:
+ Private() :
+ m_inTransaction(false)
+ {}
+
+ bool m_inTransaction;
+ MyMoneySecurity m_baseCurrency;
+ MyMoneyObjectContainer m_cache;
+ MyMoneyPriceList m_priceCache;
+
+ /**
+ * This member keeps a list of ids to notify after an
+ * operation is completed. The boolean is used as follows
+ * during processing of the list:
+ *
+ * false - don't reload the object immediately
+ * true - reload the object immediately
+ */
+ QMap<QString, bool> m_notificationList;
+
+};
+
+MyMoneyFile MyMoneyFile::file;
+
+MyMoneyFile::MyMoneyFile() :
+ d(new Private)
+{
+ m_storage = 0;
+}
+
+MyMoneyFile::~MyMoneyFile()
+{
+ _instance = 0;
+ delete m_storage;
+ delete d;
+}
+
+MyMoneyFile::MyMoneyFile(IMyMoneyStorage *storage) :
+ d(new Private)
+{
+ m_storage = 0;
+ attachStorage(storage);
+}
+
+void MyMoneyFile::attachStorage(IMyMoneyStorage* const storage)
+{
+ if(m_storage != 0)
+ throw new MYMONEYEXCEPTION("Storage already attached");
+
+ if(storage == 0)
+ throw new MYMONEYEXCEPTION("Storage must not be 0");
+
+ m_storage = storage;
+
+ // force reload of base currency
+ d->m_baseCurrency = MyMoneySecurity();
+
+ // and the whole cache
+ d->m_cache.clear(storage);
+ d->m_priceCache.clear();
+ preloadCache();
+
+ // notify application about new data availability
+ emit dataChanged();
+}
+
+void MyMoneyFile::detachStorage(IMyMoneyStorage* const /* storage */)
+{
+ d->m_cache.clear();
+ d->m_priceCache.clear();
+ m_storage = 0;
+}
+
+void MyMoneyFile::startTransaction(void)
+{
+ checkStorage();
+ if(d->m_inTransaction) {
+ throw new MYMONEYEXCEPTION("Already started a transaction!");
+ }
+
+ m_storage->startTransaction();
+ d->m_inTransaction = true;
+}
+
+bool MyMoneyFile::hasTransaction(void) const
+{
+ return d->m_inTransaction;
+}
+
+void MyMoneyFile::checkTransaction(const char* txt) const
+{
+ checkStorage();
+ if(!d->m_inTransaction) {
+ throw new MYMONEYEXCEPTION(QString("No transaction started for %1").arg(txt));
+ }
+}
+
+void MyMoneyFile::commitTransaction(void)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ bool changed = m_storage->commitTransaction();
+ d->m_inTransaction = false;
+ preloadCache();
+ if(changed) {
+ emit dataChanged();
+ }
+}
+
+void MyMoneyFile::rollbackTransaction(void)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ m_storage->rollbackTransaction();
+ d->m_inTransaction = false;
+ preloadCache();
+}
+
+void MyMoneyFile::addInstitution(MyMoneyInstitution& institution)
+{
+ // perform some checks to see that the institution stuff is OK. For
+ // now we assume that the institution must have a name, the ID is not set
+ // and it does not have a parent (MyMoneyFile).
+
+ if(institution.name().length() == 0
+ || institution.id().length() != 0)
+ throw new MYMONEYEXCEPTION("Not a new institution");
+
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addInstitution(institution);
+
+ d->m_cache.preloadInstitution(institution);
+}
+
+void MyMoneyFile::modifyInstitution(const MyMoneyInstitution& institution)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifyInstitution(institution);
+
+ addNotification(institution.id());
+}
+
+void MyMoneyFile::modifyTransaction(const MyMoneyTransaction& transaction)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ const MyMoneyTransaction* t = &transaction;
+ MyMoneyTransaction tCopy;
+
+ // now check the splits
+ bool loanAccountAffected = false;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot store split with no account assigned");
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot store split referencing standard account");
+ if(acc.isLoan() && ((*it_s).action() == MyMoneySplit::ActionTransfer))
+ loanAccountAffected = true;
+ }
+
+ // change transfer splits between asset/liability and loan accounts
+ // into amortization splits
+ if(loanAccountAffected) {
+ tCopy = transaction;
+ QValueList<MyMoneySplit> list = transaction.splits();
+ for(it_s = list.begin(); it_s != list.end(); ++it_s) {
+ if((*it_s).action() == MyMoneySplit::ActionTransfer) {
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+
+ if(acc.isAssetLiability()) {
+ MyMoneySplit s = (*it_s);
+ s.setAction(MyMoneySplit::ActionAmortization);
+ tCopy.modifySplit(s);
+ t = &tCopy;
+ }
+ }
+ }
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // get the current setting of this transaction
+ MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id());
+
+ // scan the splits again to update notification list
+ // and mark all accounts that are referenced
+ for(it_s = tr.splits().begin(); it_s != tr.splits().end(); ++it_s) {
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+
+ // perform modification
+ m_storage->modifyTransaction(*t);
+
+ // and mark all accounts that are referenced
+ for(it_s = t->splits().begin(); it_s != t->splits().end(); ++it_s) {
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+}
+
+void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount account(_account);
+
+ MyMoneyAccount acc = MyMoneyFile::account(account.id());
+
+ // check that for standard accounts only specific parameters are changed
+ if(isStandardAccount(account.id())) {
+ // make sure to use the stuff we found on file
+ account = acc;
+
+ // and only use the changes that are allowed
+ account.setName(_account.name());
+ account.setCurrencyId(_account.currencyId());
+
+ // now check that it is the same
+ if(!(account == _account))
+ throw new MYMONEYEXCEPTION("Unable to modify the standard account groups");
+ }
+
+ if(account.accountType() != acc.accountType())
+ throw new MYMONEYEXCEPTION("Unable to change account type");
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // if the account was moved to another insitution, we notify
+ // the old one as well as the new one and the structure change
+ if(acc.institutionId() != account.institutionId()) {
+ MyMoneyInstitution inst;
+ if(!acc.institutionId().isEmpty()) {
+ inst = institution(acc.institutionId());
+ inst.removeAccountId(acc.id());
+ modifyInstitution(inst);
+ }
+ if(!account.institutionId().isEmpty()) {
+ inst = institution(account.institutionId());
+ inst.addAccountId(acc.id());
+ modifyInstitution(inst);
+ }
+ addNotification(acc.institutionId());
+ addNotification(account.institutionId());
+ }
+
+ m_storage->modifyAccount(account);
+
+ addNotification(account.id());
+}
+
+void MyMoneyFile::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // check that it's not one of the standard account groups
+ if(isStandardAccount(account.id()))
+ throw new MYMONEYEXCEPTION("Unable to reparent the standard account groups");
+
+ if(account.accountGroup() == parent.accountGroup()
+ || (account.accountType() == MyMoneyAccount::Income && parent.accountType() == MyMoneyAccount::Expense)
+ || (account.accountType() == MyMoneyAccount::Expense && parent.accountType() == MyMoneyAccount::Income)) {
+
+ if(account.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Unable to reparent Stock to non-investment account");
+
+ if(parent.accountType() == MyMoneyAccount::Investment && !account.isInvest())
+ throw new MYMONEYEXCEPTION("Unable to reparent non-stock to investment account");
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // keep a notification of the current parent
+ addNotification(account.parentAccountId());
+
+ m_storage->reparentAccount(account, parent);
+
+ // and also keep one for the account itself and the new parent
+ addNotification(account.id());
+ addNotification(parent.id());
+
+ } else
+ throw new MYMONEYEXCEPTION("Unable to reparent to different account type");
+}
+
+const MyMoneyInstitution& MyMoneyFile::institution(const QString& id) const
+{
+ return d->m_cache.institution(id);
+}
+
+const MyMoneyAccount& MyMoneyFile::account(const QString& id) const
+{
+ return d->m_cache.account(id);
+}
+
+const MyMoneyAccount& MyMoneyFile::subAccountByName(const MyMoneyAccount& acc, const QString& name) const
+{
+ static MyMoneyAccount nullAccount;
+
+ QValueList<QString>::const_iterator it_a;
+ for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
+ const MyMoneyAccount& sacc = account(*it_a);
+ if(sacc.name() == name)
+ return sacc;
+ }
+ return nullAccount;
+}
+
+const MyMoneyAccount& MyMoneyFile::accountByName(const QString& name) const
+{
+ return d->m_cache.accountByName(name);
+}
+
+void MyMoneyFile::removeTransaction(const MyMoneyTransaction& transaction)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // get the engine's idea about this transaction
+ MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id());
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+
+ // scan the splits again to update notification list
+ for(it_s = tr.splits().begin(); it_s != tr.splits().end(); ++it_s) {
+ MyMoneyAccount acc = account((*it_s).accountId());
+ if(acc.isClosed())
+ throw new MYMONEYEXCEPTION(i18n("Cannot remove transaction that references a closed account."));
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+
+ m_storage->removeTransaction(transaction);
+}
+
+
+bool MyMoneyFile::hasActiveSplits(const QString& id) const
+{
+ checkStorage();
+
+ return m_storage->hasActiveSplits(id);
+}
+
+bool MyMoneyFile::isStandardAccount(const QString& id) const
+{
+ checkStorage();
+
+ return m_storage->isStandardAccount(id);
+}
+
+void MyMoneyFile::setAccountName(const QString& id, const QString& name) const
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ m_storage->setAccountName(id, name);
+}
+
+void MyMoneyFile::removeAccount(const MyMoneyAccount& account)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount parent;
+ MyMoneyAccount acc;
+ MyMoneyInstitution institution;
+
+ // check that the account and its parent exist
+ // this will throw an exception if the id is unknown
+ acc = MyMoneyFile::account(account.id());
+ parent = MyMoneyFile::account(account.parentAccountId());
+ if(!acc.institutionId().isEmpty())
+ institution = MyMoneyFile::institution(acc.institutionId());
+
+ // check that it's not one of the standard account groups
+ if(isStandardAccount(account.id()))
+ throw new MYMONEYEXCEPTION("Unable to remove the standard account groups");
+
+ if(hasActiveSplits(account.id())) {
+ throw new MYMONEYEXCEPTION("Unable to remove account with active splits");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // collect all sub-ordinate accounts for notification
+ QStringList::ConstIterator it;
+ for(it = acc.accountList().begin(); it != acc.accountList().end(); ++it)
+ addNotification(*it);
+ // don't forget the parent and a possible institution
+ addNotification(parent.id());
+ addNotification(account.institutionId());
+
+ if(!institution.id().isEmpty()) {
+ institution.removeAccountId(account.id());
+ m_storage->modifyInstitution(institution);
+ }
+ acc.setInstitutionId(QString());
+
+ m_storage->removeAccount(acc);
+ addNotification(acc.id(), false);
+ d->m_cache.clear(acc.id());
+}
+
+void MyMoneyFile::removeAccountList(const QStringList& account_list, unsigned int level) {
+ if (level > 100)
+ throw new MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::removeAccountList]!");
+
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // upon entry, we check that we could proceed with the operation
+ if(!level) {
+ if(!hasOnlyUnusedAccounts(account_list, 0))
+ throw new MYMONEYEXCEPTION("One or more accounts cannot be removed");
+
+ // NOTE: We don't use a MyMoneyNotifier here, but rather clear the whole cache
+ d->m_cache.clear();
+ }
+
+ // process all accounts in the list and test if they have transactions assigned
+ for (QStringList::const_iterator it = account_list.begin(); it != account_list.end(); ++it) {
+ MyMoneyAccount a = m_storage->account(*it);
+ //kdDebug() << "Deleting account '"<< a.name() << "'" << endl;
+
+ // first remove all sub-accounts
+ if (!a.accountList().isEmpty())
+ removeAccountList(a.accountList(), level+1);
+
+ // then remove account itself, but we first have to get
+ // rid of the account list that is still stored in
+ // the MyMoneyAccount object. Easiest way is to get a fresh copy.
+ a = m_storage->account(*it);
+ //kdDebug() << "Deleting account '"<< a2.name() << "' itself" << endl;
+ m_storage->removeAccount(a);
+ }
+}
+
+bool MyMoneyFile::hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level)
+{
+ if (level > 100)
+ throw new MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::hasOnlyUnusedAccounts]!");
+ // process all accounts in the list and test if they have transactions assigned
+ for (QStringList::const_iterator it = account_list.begin(); it != account_list.end(); ++it) {
+ if (transactionCount(*it) != 0)
+ return false; // the current account has a transaction assigned
+ if (!hasOnlyUnusedAccounts(account(*it).accountList(), level+1))
+ return false; // some sub-account has a transaction assigned
+ }
+ return true; // all subaccounts unused
+}
+
+
+void MyMoneyFile::removeInstitution(const MyMoneyInstitution& institution)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ QValueList<QString>::ConstIterator it_a;
+ MyMoneyInstitution inst = MyMoneyFile::institution(institution.id());
+
+ bool blocked = signalsBlocked();
+ blockSignals(true);
+ for(it_a = inst.accountList().begin(); it_a != inst.accountList().end(); ++it_a) {
+ MyMoneyAccount acc = account(*it_a);
+ acc.setInstitutionId(QString());
+ modifyAccount(acc);
+ }
+ blockSignals(blocked);
+
+ m_storage->removeInstitution(institution);
+
+ addNotification(institution.id(), false);
+}
+
+void MyMoneyFile::addAccount(MyMoneyAccount& account, MyMoneyAccount& parent)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyInstitution institution;
+
+ // perform some checks to see that the account stuff is OK. For
+ // now we assume that the account must have a name, has no
+ // transaction and sub-accounts and parent account
+ // it's own ID is not set and it does not have a pointer to (MyMoneyFile)
+
+ if(account.name().length() == 0)
+ throw new MYMONEYEXCEPTION("Account has no name");
+
+ if(account.id().length() != 0)
+ throw new MYMONEYEXCEPTION("New account must have no id");
+
+ if(account.accountList().count() != 0)
+ throw new MYMONEYEXCEPTION("New account must have no sub-accounts");
+
+ if(!account.parentAccountId().isEmpty())
+ throw new MYMONEYEXCEPTION("New account must have no parent-id");
+
+ if(account.accountType() == MyMoneyAccount::UnknownAccountType)
+ throw new MYMONEYEXCEPTION("Account has invalid type");
+
+ // make sure, that the parent account exists
+ // if not, an exception is thrown. If it exists,
+ // get a copy of the current data
+ MyMoneyAccount acc = MyMoneyFile::account(parent.id());
+
+#if 0
+ // TODO: remove the following code as we now can have multiple accounts
+ // with the same name even in the same hierarchy position of the account tree
+ //
+ // check if the selected name is currently not among the child accounts
+ // if we find one, then return it as the new account
+ QStringList::const_iterator it_a;
+ for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) {
+ MyMoneyAccount a = MyMoneyFile::account(*it_a);
+ if(account.name() == a.name()) {
+ account = a;
+ return;
+ }
+ }
+#endif
+
+ // FIXME: make sure, that the parent has the same type
+ // I left it out here because I don't know, if there is
+ // a tight coupling between e.g. checking accounts and the
+ // class asset. It certainly does not make sense to create an
+ // expense account under an income account. Maybe it does, I don't know.
+
+ // We enforce, that a stock account can never be a parent and
+ // that the parent for a stock account must be an investment. Also,
+ // an investment cannot have another investment account as it's parent
+ if(parent.isInvest())
+ throw new MYMONEYEXCEPTION("Stock account cannot be parent account");
+
+ if(account.isInvest() && parent.accountType() != MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Stock account must have investment account as parent ");
+
+ if(!account.isInvest() && parent.accountType() == MyMoneyAccount::Investment)
+ throw new MYMONEYEXCEPTION("Investment account can only have stock accounts as children");
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // if an institution is set, verify that it exists
+ if(account.institutionId().length() != 0) {
+ // check the presence of the institution. if it
+ // does not exist, an exception is thrown
+ institution = MyMoneyFile::institution(account.institutionId());
+ }
+
+
+ if(!account.openingDate().isValid()) {
+ account.setOpeningDate(QDate::currentDate());
+ }
+
+ account.setParentAccountId(parent.id());
+
+ m_storage->addAccount(account);
+ m_storage->addAccount(parent, account);
+
+ if(account.institutionId().length() != 0) {
+ institution.addAccountId(account.id());
+ m_storage->modifyInstitution(institution);
+ addNotification(institution.id());
+ }
+
+ d->m_cache.preloadAccount(account);
+ addNotification(parent.id());
+}
+
+MyMoneyTransaction MyMoneyFile::createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance)
+{
+ MyMoneyTransaction t;
+ // if the opening balance is not zero, we need
+ // to create the respective transaction
+ if(!balance.isZero()) {
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneySecurity currency = security(acc.currencyId());
+ MyMoneyAccount openAcc = openingBalanceAccount(currency);
+
+ if(openAcc.openingDate() > acc.openingDate()) {
+ openAcc.setOpeningDate(acc.openingDate());
+ modifyAccount(openAcc);
+ }
+
+ MyMoneySplit s;
+
+ t.setPostDate(acc.openingDate());
+ t.setCommodity(acc.currencyId());
+
+ s.setAccountId(acc.id());
+ s.setShares(balance);
+ s.setValue(balance);
+ t.addSplit(s);
+
+ s.clearId();
+ s.setAccountId(openAcc.id());
+ s.setShares(-balance);
+ s.setValue(-balance);
+ t.addSplit(s);
+
+ addTransaction(t);
+ }
+ return t;
+}
+
+QString MyMoneyFile::openingBalanceTransaction(const MyMoneyAccount& acc) const
+{
+ QString result;
+
+ MyMoneySecurity currency = security(acc.currencyId());
+ MyMoneyAccount openAcc;
+
+ try
+ {
+ openAcc = openingBalanceAccount(currency);
+ }
+ catch(MyMoneyException *e)
+ {
+ delete e;
+ return result;
+ }
+
+ // Iterate over all the opening balance transactions for this currency
+ MyMoneyTransactionFilter filter;
+ filter.addAccount(openAcc.id());
+ QValueList<MyMoneyTransaction> transactions = transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+ while ( it_t != transactions.end() )
+ {
+ try
+ {
+ // Test whether the transaction also includes a split into
+ // this account
+ (*it_t).splitByAccount(acc.id(), true /*match*/);
+
+ // If so, we have a winner!
+ result = (*it_t).id();
+ break;
+ }
+ catch(MyMoneyException *e)
+ {
+ // If not, keep searching
+ ++it_t;
+ delete e;
+ }
+ }
+
+ return result;
+}
+
+const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security)
+{
+ if(!security.isCurrency())
+ throw new MYMONEYEXCEPTION("Opening balance for non currencies not supported");
+
+ try
+ {
+ return openingBalanceAccount_internal(security);
+ }
+ catch(MyMoneyException *e)
+ {
+ delete e;
+ MyMoneyFileTransaction ft;
+ MyMoneyAccount acc;
+
+ try {
+ acc = createOpeningBalanceAccount(security);
+ ft.commit();
+
+ } catch(MyMoneyException* e) {
+ qDebug("Unable to create opening balance account for security %s", security.id().data());
+ delete e;
+ }
+ return acc;
+ }
+}
+
+const MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const
+{
+ return openingBalanceAccount_internal(security);
+}
+
+const MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const
+{
+ if(!security.isCurrency())
+ throw new MYMONEYEXCEPTION("Opening balance for non currencies not supported");
+
+ MyMoneyAccount acc;
+ QRegExp match(QString("^%1").arg(i18n(MyMoneyFile::OpeningBalancesPrefix)));
+
+ QValueList<MyMoneyAccount> accounts;
+ QValueList<MyMoneyAccount>::Iterator it;
+
+ accountList(accounts, equity().accountList(), true);
+
+ for(it = accounts.begin(); it != accounts.end(); ++it) {
+ if(match.search((*it).name()) != -1) {
+ if((*it).currencyId() == security.id()) {
+ acc = *it;
+ break;
+ }
+ }
+ }
+
+ if(acc.id().isEmpty()) {
+ throw new MYMONEYEXCEPTION(QString("No opening balance account for %1").arg(security.tradingSymbol()));
+ }
+
+ return acc;
+}
+
+const MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount acc;
+ QString name(i18n(MyMoneyFile::OpeningBalancesPrefix));
+ if(security.id() != baseCurrency().id()) {
+ name += QString(" (%1)").arg(security.id());
+ }
+ acc.setName(name);
+ acc.setAccountType(MyMoneyAccount::Equity);
+ acc.setCurrencyId(security.id());
+
+ MyMoneyAccount parent = equity();
+ this->addAccount(acc, parent);
+ return acc;
+}
+
+void MyMoneyFile::addTransaction(MyMoneyTransaction& transaction)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // perform some checks to see that the transaction stuff is OK. For
+ // now we assume that
+ // * no ids are assigned
+ // * the date valid (must not be empty)
+ // * the referenced accounts in the splits exist
+
+ // first perform all the checks
+ if(!transaction.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Unable to add transaction with id set");
+ if(!transaction.postDate().isValid())
+ throw new MYMONEYEXCEPTION("Unable to add transaction with invalid postdate");
+
+ // now check the splits
+ bool loanAccountAffected = false;
+ QValueList<MyMoneySplit>::ConstIterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist or is one of the standard accounts
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot add split with no account assigned");
+ if(acc.isLoan())
+ loanAccountAffected = true;
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot add split referencing standard account");
+ }
+
+ // change transfer splits between asset/liability and loan accounts
+ // into amortization splits
+ if(loanAccountAffected) {
+ QValueList<MyMoneySplit> list = transaction.splits();
+ for(it_s = list.begin(); it_s != list.end(); ++it_s) {
+ if((*it_s).action() == MyMoneySplit::ActionTransfer) {
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+
+ if(acc.isAssetLiability()) {
+ MyMoneySplit s = (*it_s);
+ s.setAction(MyMoneySplit::ActionAmortization);
+ transaction.modifySplit(s);
+ }
+ }
+ }
+ }
+
+ // check that we have a commodity
+ if(transaction.commodity().isEmpty()) {
+ transaction.setCommodity(baseCurrency().id());
+ }
+
+ // then add the transaction to the file global pool
+ m_storage->addTransaction(transaction);
+
+ // scan the splits again to update notification list
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ addNotification((*it_s).accountId());
+ addNotification((*it_s).payeeId());
+ }
+}
+
+const MyMoneyTransaction MyMoneyFile::transaction(const QString& id) const
+{
+ checkStorage();
+
+ return m_storage->transaction(id);
+}
+
+const MyMoneyTransaction MyMoneyFile::transaction(const QString& account, const int idx) const
+{
+ checkStorage();
+
+ return m_storage->transaction(account, idx);
+}
+
+void MyMoneyFile::addPayee(MyMoneyPayee& payee)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addPayee(payee);
+
+ d->m_cache.preloadPayee(payee);
+}
+
+const MyMoneyPayee& MyMoneyFile::payee(const QString& id) const
+{
+ return d->m_cache.payee(id);
+}
+
+const MyMoneyPayee& MyMoneyFile::payeeByName(const QString& name) const
+{
+ checkStorage();
+
+ return d->m_cache.payee(m_storage->payeeByName(name).id());
+}
+
+void MyMoneyFile::modifyPayee(const MyMoneyPayee& payee)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ addNotification(payee.id());
+
+ m_storage->modifyPayee(payee);
+}
+
+void MyMoneyFile::removePayee(const MyMoneyPayee& payee)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removePayee(payee);
+
+ addNotification(payee.id(), false);
+}
+
+void MyMoneyFile::accountList(QValueList<MyMoneyAccount>& list, const QStringList& idlist, const bool recursive) const
+{
+ if(idlist.isEmpty()) {
+ d->m_cache.account(list);
+
+#if 0
+ // TODO: I have no idea what this was good for, but it caused the networth report
+ // to show double the numbers so I commented it out (ipwizard, 2008-05-24)
+ if (m_storage && (list.isEmpty() || list.size() != m_storage->accountCount())) {
+ m_storage->accountList(list);
+ d->m_cache.preloadAccount(list);
+ }
+#endif
+
+ QValueList<MyMoneyAccount>::Iterator it;
+ QValueList<MyMoneyAccount>::Iterator next;
+ for(it = list.begin(); it != list.end(); ) {
+ if(isStandardAccount( (*it).id() )) {
+ it = list.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ } else {
+ QValueList<MyMoneyAccount>::ConstIterator it;
+ QValueList<MyMoneyAccount> list_a;
+ d->m_cache.account(list_a);
+
+ for(it = list_a.begin(); it != list_a.end(); ++it) {
+ if(!isStandardAccount((*it).id())) {
+ if(idlist.findIndex((*it).id()) != -1) {
+ list.append(*it);
+ if(recursive == true) {
+ accountList(list, (*it).accountList(), true);
+ }
+ }
+ }
+ }
+ }
+}
+
+void MyMoneyFile::institutionList(QValueList<MyMoneyInstitution>& list) const
+{
+ d->m_cache.institution(list);
+}
+
+const QValueList<MyMoneyInstitution> MyMoneyFile::institutionList(void) const
+{
+ QValueList<MyMoneyInstitution> list;
+ institutionList(list);
+ return list;
+}
+
+// general get functions
+const MyMoneyPayee MyMoneyFile::user(void) const { checkStorage(); return m_storage->user(); }
+
+// general set functions
+void MyMoneyFile::setUser(const MyMoneyPayee& user)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->setUser(user);
+}
+
+bool MyMoneyFile::dirty(void) const
+{
+ if(!m_storage)
+ return false;
+
+ return m_storage->dirty();
+}
+
+void MyMoneyFile::setDirty(void) const
+{
+ checkStorage();
+
+ m_storage->setDirty();
+}
+
+unsigned int MyMoneyFile::accountCount(void) const
+{
+ checkStorage();
+
+ return m_storage->accountCount();
+}
+
+void MyMoneyFile::ensureDefaultCurrency(MyMoneyAccount& acc) const
+{
+ if(acc.currencyId().isEmpty()) {
+ if(!baseCurrency().id().isEmpty())
+ acc.setCurrencyId(baseCurrency().id());
+ }
+}
+
+const MyMoneyAccount& MyMoneyFile::liability(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_LIABILITY);
+}
+
+const MyMoneyAccount& MyMoneyFile::asset(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_ASSET);
+}
+
+const MyMoneyAccount& MyMoneyFile::expense(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_EXPENSE);
+}
+
+const MyMoneyAccount& MyMoneyFile::income(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_INCOME);
+}
+
+const MyMoneyAccount& MyMoneyFile::equity(void) const
+{
+ checkStorage();
+
+ return d->m_cache.account (STD_ACC_EQUITY);
+}
+
+unsigned int MyMoneyFile::transactionCount(const QString& account) const
+{
+ checkStorage();
+
+ return m_storage->transactionCount(account);
+}
+
+const QMap<QString, unsigned long> MyMoneyFile::transactionCountMap(void) const
+{
+ checkStorage();
+
+ return m_storage->transactionCountMap();
+}
+
+unsigned int MyMoneyFile::institutionCount(void) const
+{
+ checkStorage();
+
+ return m_storage->institutionCount();
+}
+
+const MyMoneyMoney MyMoneyFile::balance(const QString& id, const QDate& date) const
+{
+ checkStorage();
+
+ return m_storage->balance(id, date);
+}
+
+const MyMoneyMoney MyMoneyFile::totalBalance(const QString& id, const QDate& date) const
+{
+ checkStorage();
+
+ return m_storage->totalBalance(id, date);
+}
+
+void MyMoneyFile::warningMissingRate(const QString& fromId, const QString& toId) const
+{
+ MyMoneySecurity from, to;
+ try {
+ from = security(fromId);
+ to = security(toId);
+ qWarning("Missing price info for conversion from %s to %s", from.name().latin1(), to.name().latin1());
+
+ } catch(MyMoneyException *e) {
+ qFatal("Missing security caught in MyMoneyFile::warningMissingRate(): %s(%ld) %s", e->file().data(), e->line(), e->what().data());
+ delete e;
+ }
+}
+
+void MyMoneyFile::notify(void)
+{
+ QMap<QString, bool>::ConstIterator it;
+ for(it = d->m_notificationList.begin(); it != d->m_notificationList.end(); ++it) {
+ if(*it)
+ d->m_cache.refresh(it.key());
+ else
+ d->m_cache.clear(it.key());
+ }
+ clearNotification();
+}
+
+void MyMoneyFile::addNotification(const QString& id, bool reload)
+{
+ if(!id.isEmpty())
+ d->m_notificationList[id] = reload;
+}
+
+void MyMoneyFile::clearNotification()
+{
+ // reset list to be empty
+ d->m_notificationList.clear();
+}
+
+void MyMoneyFile::transactionList(QValueList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
+{
+ checkStorage();
+ m_storage->transactionList(list, filter);
+}
+
+void MyMoneyFile::transactionList(QValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
+{
+ checkStorage();
+ m_storage->transactionList(list, filter);
+}
+
+const QValueList<MyMoneyTransaction> MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const
+{
+ QValueList<MyMoneyTransaction> list;
+ transactionList(list, filter);
+ return list;
+}
+
+const QValueList<MyMoneyPayee> MyMoneyFile::payeeList(void) const
+{
+ QValueList<MyMoneyPayee> list;
+ d->m_cache.payee(list);
+ return list;
+}
+
+QString MyMoneyFile::accountToCategory(const QString& accountId, bool includeStandardAccounts) const
+{
+ MyMoneyAccount acc;
+ QString rc;
+
+ if(!accountId.isEmpty()) {
+ acc = account(accountId);
+ do {
+ if(!rc.isEmpty())
+ rc = AccountSeperator + rc;
+ rc = acc.name() + rc;
+ acc = account(acc.parentAccountId());
+ } while(!acc.id().isEmpty() && (includeStandardAccounts || !isStandardAccount(acc.id())));
+ }
+ return rc;
+}
+
+QString MyMoneyFile::categoryToAccount(const QString& category, MyMoneyAccount::accountTypeE type) const
+{
+ QString id;
+
+ // search the category in the expense accounts and if it is not found, try
+ // to locate it in the income accounts
+ if(type == MyMoneyAccount::UnknownAccountType
+ || type == MyMoneyAccount::Expense) {
+ id = locateSubAccount(MyMoneyFile::instance()->expense(), category);
+ }
+
+ if((id.isEmpty() && type == MyMoneyAccount::UnknownAccountType)
+ || type == MyMoneyAccount::Income) {
+ id = locateSubAccount(MyMoneyFile::instance()->income(), category);
+ }
+
+ return id;
+}
+
+QString MyMoneyFile::nameToAccount(const QString& name) const
+{
+ QString id;
+
+ // search the category in the asset accounts and if it is not found, try
+ // to locate it in the liability accounts
+ id = locateSubAccount(MyMoneyFile::instance()->asset(), name);
+ if(id.isEmpty())
+ id = locateSubAccount(MyMoneyFile::instance()->liability(), name);
+
+ return id;
+}
+
+QString MyMoneyFile::parentName(const QString& name) const
+{
+ return name.section(AccountSeperator, 0, -2);
+}
+
+QString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const QString& category) const
+{
+ MyMoneyAccount nextBase;
+ QString level, remainder;
+ level = category.section(AccountSeperator, 0, 0);
+ remainder = category.section(AccountSeperator, 1);
+
+ QStringList list = base.accountList();
+ QStringList::ConstIterator it_a;
+
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ nextBase = account(*it_a);
+ if(nextBase.name() == level) {
+ if(remainder.isEmpty()) {
+ return nextBase.id();
+ }
+ return locateSubAccount(nextBase, remainder);
+ }
+ }
+ return QString();
+}
+
+QString MyMoneyFile::value(const QString& key) const
+{
+ checkStorage();
+
+ return m_storage->value(key);
+}
+
+void MyMoneyFile::setValue(const QString& key, const QString& val)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->setValue(key, val);
+}
+
+void MyMoneyFile::deletePair(const QString& key)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->deletePair(key);
+}
+
+void MyMoneyFile::addSchedule(MyMoneySchedule& sched)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyTransaction transaction = sched.transaction();
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist or is one of the standard accounts
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot add split with no account assigned");
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot add split referencing standard account");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addSchedule(sched);
+}
+
+void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyTransaction transaction = sched.transaction();
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) {
+ // the following line will throw an exception if the
+ // account does not exist or is one of the standard accounts
+ MyMoneyAccount acc = MyMoneyFile::account((*it_s).accountId());
+ if(acc.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Cannot store split with no account assigned");
+ if(isStandardAccount((*it_s).accountId()))
+ throw new MYMONEYEXCEPTION("Cannot store split referencing standard account");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifySchedule(sched);
+
+ addNotification(sched.id());
+}
+
+void MyMoneyFile::removeSchedule(const MyMoneySchedule& sched)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeSchedule(sched);
+
+ addNotification(sched.id(), false);
+}
+
+const MyMoneySchedule MyMoneyFile::schedule(const QString& id) const
+{
+ return d->m_cache.schedule(id);
+}
+
+const QValueList<MyMoneySchedule> MyMoneyFile::scheduleList(
+ const QString& accountId,
+ const MyMoneySchedule::typeE type,
+ const MyMoneySchedule::occurenceE occurence,
+ const MyMoneySchedule::paymentTypeE paymentType,
+ const QDate& startDate,
+ const QDate& endDate,
+ const bool overdue) const
+{
+ checkStorage();
+
+ return m_storage->scheduleList(accountId, type, occurence, paymentType, startDate, endDate, overdue);
+}
+
+
+const QStringList MyMoneyFile::consistencyCheck(void)
+{
+ QValueList<MyMoneyAccount> list;
+ QValueList<MyMoneyAccount>::Iterator it_a;
+ QValueList<MyMoneySchedule>::Iterator it_sch;
+ QValueList<MyMoneyPayee>::Iterator it_p;
+ QValueList<MyMoneyTransaction>::Iterator it_t;
+ QValueList<MyMoneyReport>::Iterator it_r;
+ QStringList accountRebuild;
+ QStringList::ConstIterator it_c;
+
+ QMap<QString, bool> interestAccounts;
+
+ MyMoneyAccount parent;
+ MyMoneyAccount child;
+ MyMoneyAccount toplevel;
+
+ QString parentId;
+ QStringList rc;
+
+ int problemCount = 0;
+ int unfixedCount = 0;
+ QString problemAccount;
+
+ // check that we have a storage object
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // get the current list of accounts
+ accountList(list);
+ // add the standard accounts
+ list << MyMoneyFile::instance()->asset();
+ list << MyMoneyFile::instance()->liability();
+ list << MyMoneyFile::instance()->income();
+ list << MyMoneyFile::instance()->expense();
+
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ // no more checks for standard accounts
+ if(isStandardAccount((*it_a).id())) {
+ continue;
+ }
+
+ switch((*it_a).accountGroup()) {
+ case MyMoneyAccount::Asset:
+ toplevel = asset();
+ break;
+ case MyMoneyAccount::Liability:
+ toplevel = liability();
+ break;
+ case MyMoneyAccount::Expense:
+ toplevel = expense();
+ break;
+ case MyMoneyAccount::Income:
+ toplevel = income();
+ break;
+ case MyMoneyAccount::Equity:
+ toplevel = equity();
+ break;
+ default:
+ qWarning("%s:%d This should never happen!", __FILE__ , __LINE__);
+ break;
+ }
+
+ // check for loops in the hierarchy
+ parentId = (*it_a).parentAccountId();
+ try {
+ bool dropOut = false;
+ while(!isStandardAccount(parentId) && !dropOut) {
+ parent = account(parentId);
+ if(parent.id() == (*it_a).id()) {
+ // parent loops, so we need to re-parent to toplevel account
+ // find parent account in our list
+ problemCount++;
+ QValueList<MyMoneyAccount>::Iterator it_b;
+ for(it_b = list.begin(); it_b != list.end(); ++it_b) {
+ if((*it_b).id() == parent.id()) {
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ rc << i18n(" * Loop detected between this account and account '%2'.").arg((*it_b).name());
+ rc << i18n(" Reparenting account '%2' to top level account '%1'.").arg(toplevel.name(), (*it_a).name());
+ (*it_a).setParentAccountId(toplevel.id());
+ if(accountRebuild.contains(toplevel.id()) == 0)
+ accountRebuild << toplevel.id();
+ if(accountRebuild.contains((*it_a).id()) == 0)
+ accountRebuild << (*it_a).id();
+ dropOut = true;
+ break;
+ }
+ }
+ }
+ }
+ parentId = parent.parentAccountId();
+ }
+
+ } catch(MyMoneyException *e) {
+ // if we don't know about a parent, we catch it later
+ delete e;
+ }
+
+ // check that the parent exists
+ parentId = (*it_a).parentAccountId();
+ try {
+ parent = account(parentId);
+ if((*it_a).accountGroup() != parent.accountGroup()) {
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ // the parent belongs to a different group, so we reconnect to the
+ // master group account (asset, liability, etc) to which this account
+ // should belong and update it in the engine.
+ rc << i18n(" * Parent account '%1' belongs to a different group.").arg(parent.name());
+ rc << i18n(" New parent account is the top level account '%1'.").arg(toplevel.name());
+ (*it_a).setParentAccountId(toplevel.id());
+
+ // make sure to rebuild the sub-accounts of the top account
+ // and the one we removed this account from
+ if(accountRebuild.contains(toplevel.id()) == 0)
+ accountRebuild << toplevel.id();
+ if(accountRebuild.contains(parent.id()) == 0)
+ accountRebuild << parent.id();
+ } else if(!parent.accountList().contains((*it_a).id())) {
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ // parent exists, but does not have a reference to the account
+ rc << i18n(" * Parent account '%1' does not contain '%2' as sub-account.").arg(parent.name(), problemAccount);
+ if(accountRebuild.contains(parent.id()) == 0)
+ accountRebuild << parent.id();
+ }
+ } catch(MyMoneyException *e) {
+ delete e;
+ // apparently, the parent does not exist anymore. we reconnect to the
+ // master group account (asset, liability, etc) to which this account
+ // should belong and update it in the engine.
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ rc << i18n(" * The parent with id %1 does not exist anymore.").arg(parentId);
+ rc << i18n(" New parent account is the top level account '%1'.").arg(toplevel.name());
+ (*it_a).setParentAccountId(toplevel.id());
+
+ addNotification((*it_a).id());
+
+ // make sure to rebuild the sub-accounts of the top account
+ if(accountRebuild.contains(toplevel.id()) == 0)
+ accountRebuild << toplevel.id();
+ }
+
+ // now check that all the children exist and have the correct type
+ for(it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end(); ++it_c) {
+ // check that the child exists
+ try {
+ child = account(*it_c);
+ } catch(MyMoneyException *e) {
+ problemCount++;
+ if(problemAccount != (*it_a).name()) {
+ problemAccount = (*it_a).name();
+ rc << i18n("* Problem with account '%1'").arg(problemAccount);
+ }
+ rc << i18n(" * Child account with id %1 does not exist anymore.").arg(*it_c);
+ rc << i18n(" The child account list will be reconstructed.");
+ if(accountRebuild.contains((*it_a).id()) == 0)
+ accountRebuild << (*it_a).id();
+ }
+ }
+
+ // see if it is a loan account. if so, remember the assigned interest account
+ if((*it_a).isLoan()) {
+ const MyMoneyAccountLoan* loan = dynamic_cast<MyMoneyAccountLoan*>(&(*it_a));
+ if(!loan->interestAccountId().isEmpty())
+ interestAccounts[loan->interestAccountId()] = true;
+ }
+
+ // if the account was modified, we need to update it in the engine
+ if(!(m_storage->account((*it_a).id()) == (*it_a))) {
+ try {
+ m_storage->modifyAccount(*it_a, true);
+ addNotification((*it_a).id());
+ } catch (MyMoneyException *e) {
+ delete e;
+ rc << i18n(" * Unable to update account data in engine.");
+ return rc;
+ }
+ }
+ }
+
+ if(accountRebuild.count() != 0) {
+ rc << i18n("* Reconstructing the child lists for");
+ }
+
+ // clear the affected lists
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ if(accountRebuild.contains((*it_a).id())) {
+ rc << QString(" %1").arg((*it_a).name());
+ // clear the account list
+ for(it_c = (*it_a).accountList().begin(); it_c != (*it_a).accountList().end();) {
+ (*it_a).removeAccountId(*it_c);
+ it_c = (*it_a).accountList().begin();
+ }
+ }
+ }
+
+ // reconstruct the lists
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ QValueList<MyMoneyAccount>::Iterator it;
+ parentId = (*it_a).parentAccountId();
+ if(accountRebuild.contains(parentId)) {
+ for(it = list.begin(); it != list.end(); ++it) {
+ if((*it).id() == parentId) {
+ (*it).addAccountId((*it_a).id());
+ break;
+ }
+ }
+ }
+ }
+
+ // update the engine objects
+ for(it_a = list.begin(); it_a != list.end(); ++it_a) {
+ if(accountRebuild.contains((*it_a).id())) {
+ try {
+ m_storage->modifyAccount(*it_a, true);
+ addNotification((*it_a).id());
+ } catch (MyMoneyException *e) {
+ delete e;
+ rc << i18n(" * Unable to update account data for account %1 in engine").arg((*it_a).name());
+ }
+ }
+ }
+
+ // For some reason, files exist with invalid ids. This has been found in the payee id
+ // so we fix them here
+ QValueList<MyMoneyPayee> pList = payeeList();
+ QMap<QString, QString>payeeConversionMap;
+
+ for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
+ if((*it_p).id().length() > 7) {
+ // found one of those with an invalid ids
+ // create a new one and store it in the map.
+ MyMoneyPayee payee = (*it_p);
+ payee.clearId();
+ m_storage->addPayee(payee);
+ payeeConversionMap[(*it_p).id()] = payee.id();
+ rc << i18n(" * Payee %1 recreated with fixed id").arg(payee.name());
+ ++problemCount;
+ }
+ }
+
+ // Fix the transactions
+ QValueList<MyMoneyTransaction> tList;
+ MyMoneyTransactionFilter filter;
+ filter.setReportAllSplits(false);
+ m_storage->transactionList(tList, filter);
+ // Generate the list of interest accounts
+ for(it_t = tList.begin(); it_t != tList.end(); ++it_t) {
+ const MyMoneyTransaction& t = (*it_t);
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ if((*it_s).action() == MyMoneySplit::ActionInterest)
+ interestAccounts[(*it_s).accountId()] = true;
+ }
+ }
+ for(it_t = tList.begin(); it_t != tList.end(); ++it_t) {
+ MyMoneyTransaction t = (*it_t);
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ bool tChanged = false;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ bool sChanged = false;
+ MyMoneySplit s = (*it_s);
+ if(payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
+ s.setPayeeId(payeeConversionMap[s.payeeId()]);
+ sChanged = true;
+ rc << i18n(" * Payee id updated in split of transaction '%1'.").arg(t.id());
+ ++problemCount;
+ }
+
+ // make sure, that shares and value have the same number if they
+ // represent the same currency.
+ try {
+ const MyMoneyAccount& acc = this->account(s.accountId());
+ if(t.commodity() == acc.currencyId()
+ && s.shares().reduce() != s.value().reduce()) {
+ // use the value as master if the transaction is balanced
+ if(t.splitSum().isZero()) {
+ s.setShares(s.value());
+ rc << i18n(" * shares set to value in split of transaction '%1'.").arg(t.id());
+ } else {
+ s.setValue(s.shares());
+ rc << i18n(" * value set to shares in split of transaction '%1'.").arg(t.id());
+ }
+ sChanged = true;
+ ++problemCount;
+ }
+ } catch(MyMoneyException *e) {
+ rc << i18n(" * Split %2 in transaction '%1' contains a reference to invalid account %3. Please fix manually.").arg(t.id(), (*it_s).id(), (*it_s).accountId());
+ ++problemCount;
+ ++unfixedCount;
+ delete e;
+ }
+
+ // make sure the interest splits are marked correct as such
+ if(interestAccounts.find(s.accountId()) != interestAccounts.end()
+ && s.action() != MyMoneySplit::ActionInterest) {
+ s.setAction(MyMoneySplit::ActionInterest);
+ sChanged = true;
+ rc << i18n(" * action marked as interest in split of transaction '%1'.").arg(t.id());
+ ++problemCount;
+ }
+
+ if(sChanged) {
+ tChanged = true;
+ t.modifySplit(s);
+ }
+ }
+ if(tChanged) {
+ m_storage->modifyTransaction(t);
+ }
+ }
+
+ // Fix the schedules
+ QValueList<MyMoneySchedule> schList = scheduleList();
+ for(it_sch = schList.begin(); it_sch != schList.end(); ++it_sch) {
+ MyMoneySchedule sch = (*it_sch);
+ MyMoneyTransaction t = sch.transaction();
+ bool tChanged = false;
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ MyMoneySplit s = (*it_s);
+ bool sChanged = false;
+ if(payeeConversionMap.find((*it_s).payeeId()) != payeeConversionMap.end()) {
+ s.setPayeeId(payeeConversionMap[s.payeeId()]);
+ sChanged = true;
+ rc << i18n(" * Payee id updated in split of schedule '%1'.").arg((*it_sch).name());
+ ++problemCount;
+ }
+ if(!(*it_s).value().isZero() && (*it_s).shares().isZero()) {
+ s.setShares(s.value());
+ sChanged = true;
+ rc << i18n(" * Split in scheduled transaction '%1' contained value != 0 and shares == 0.").arg((*it_sch).name());
+ rc << i18n(" Shares set to value.");
+ ++problemCount;
+ }
+
+ // make sure, we don't have a bankid stored with a split in a schedule
+ if(!(*it_s).bankID().isEmpty()) {
+ s.setBankID(QString());
+ sChanged = true;
+ rc << i18n(" * Removed bankid from split in scheduled transaction '%1'.").arg((*it_sch).name());
+ ++problemCount;
+ }
+
+ // make sure, that shares and value have the same number if they
+ // represent the same currency.
+ try {
+ const MyMoneyAccount& acc = this->account(s.accountId());
+ if(t.commodity() == acc.currencyId()
+ && s.shares().reduce() != s.value().reduce()) {
+ // use the value as master if the transaction is balanced
+ if(t.splitSum().isZero()) {
+ s.setShares(s.value());
+ rc << i18n(" * shares set to value in split in schedule '%1'.").arg((*it_sch).name());
+ } else {
+ s.setValue(s.shares());
+ rc << i18n(" * value set to shares in split in schedule '%1'.").arg((*it_sch).name());
+ }
+ sChanged = true;
+ ++problemCount;
+ }
+ } catch(MyMoneyException *e) {
+ rc << i18n(" * Split %2 in schedule '%1' contains a reference to invalid account %3. Please fix manually.").arg((*it_sch).name(), (*it_s).id(), (*it_s).accountId());
+ ++problemCount;
+ ++unfixedCount;
+ delete e;
+ }
+ if(sChanged) {
+ t.modifySplit(s);
+ tChanged = true;
+ }
+ }
+ if(tChanged) {
+ sch.setTransaction(t);
+ m_storage->modifySchedule(sch);
+ }
+ }
+
+ // Fix the reports
+ QValueList<MyMoneyReport> rList = reportList();
+ for(it_r = rList.begin(); it_r != rList.end(); ++it_r) {
+ MyMoneyReport r = *it_r;
+ QStringList pList;
+ QStringList::Iterator it_p;
+ (*it_r).payees(pList);
+ bool rChanged = false;
+ for(it_p = pList.begin(); it_p != pList.end(); ++it_p) {
+ if(payeeConversionMap.find(*it_p) != payeeConversionMap.end()) {
+ rc << i18n(" * Payee id updated in report '%1'.").arg((*it_r).name());
+ ++problemCount;
+ r.removeReference(*it_p);
+ r.addPayee(payeeConversionMap[*it_p]);
+ rChanged = true;
+ }
+ }
+ if(rChanged) {
+ m_storage->modifyReport(r);
+ }
+ }
+
+ // erase old payee ids
+ QMap<QString, QString>::Iterator it_m;
+ for(it_m = payeeConversionMap.begin(); it_m != payeeConversionMap.end(); ++it_m) {
+ MyMoneyPayee payee = this->payee(it_m.key());
+ removePayee(payee);
+ rc << i18n(" * Payee '%1' removed.").arg(payee.id());
+ ++problemCount;
+ }
+
+ // add more checks here
+
+ if(problemCount == 0)
+ rc << i18n("Finish! Data is consistent.");
+ else
+ rc << i18n("Finish! %1 problem(s) corrected. %2 problem(s) still present.")
+ .arg(problemCount).arg(unfixedCount);
+
+ return rc;
+}
+
+QString MyMoneyFile::createCategory(const MyMoneyAccount& base, const QString& name)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ MyMoneyAccount parent = base;
+ QString categoryText;
+
+ if(base.id() != expense().id() && base.id() != income().id())
+ throw new MYMONEYEXCEPTION("Invalid base category");
+
+ QStringList subAccounts = QStringList::split(AccountSeperator, name);
+ QStringList::Iterator it;
+ for (it = subAccounts.begin(); it != subAccounts.end(); ++it)
+ {
+ MyMoneyAccount categoryAccount;
+
+ categoryAccount.setName(*it);
+ categoryAccount.setAccountType(base.accountType());
+
+ if (it == subAccounts.begin())
+ categoryText += *it;
+ else
+ categoryText += (AccountSeperator + *it);
+
+ // Only create the account if it doesn't exist
+ try
+ {
+ QString categoryId = categoryToAccount(categoryText);
+ if (categoryId.isEmpty())
+ addAccount(categoryAccount, parent);
+ else
+ {
+ categoryAccount = account(categoryId);
+ }
+ }
+ catch (MyMoneyException *e)
+ {
+ qDebug("Unable to add account %s, %s, %s: %s",
+ categoryAccount.name().latin1(),
+ parent.name().latin1(),
+ categoryText.latin1(),
+ e->what().latin1());
+ delete e;
+ }
+
+ parent = categoryAccount;
+ }
+
+ return categoryToAccount(name);
+}
+
+const QValueList<MyMoneySchedule> MyMoneyFile::scheduleListEx( int scheduleTypes,
+ int scheduleOcurrences,
+ int schedulePaymentTypes,
+ QDate startDate,
+ const QStringList& accounts) const
+{
+ checkStorage();
+
+ return m_storage->scheduleListEx(scheduleTypes, scheduleOcurrences, schedulePaymentTypes, startDate, accounts);
+}
+
+void MyMoneyFile::addSecurity(MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addSecurity(security);
+
+ d->m_cache.preloadSecurity(security);
+}
+
+void MyMoneyFile::modifySecurity(const MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifySecurity(security);
+
+ addNotification(security.id());
+}
+
+void MyMoneyFile::removeSecurity(const MyMoneySecurity& security)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeSecurity(security);
+
+ addNotification(security.id(), false);
+}
+
+const MyMoneySecurity& MyMoneyFile::security(const QString& id) const
+{
+ if(id.isEmpty())
+ return baseCurrency();
+
+ return d->m_cache.security(id);
+}
+
+const QValueList<MyMoneySecurity> MyMoneyFile::securityList(void) const
+{
+ checkStorage();
+
+ return m_storage->securityList();
+}
+
+void MyMoneyFile::addCurrency(const MyMoneySecurity& currency)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addCurrency(currency);
+
+ // we can't really use addNotification here, because there is
+ // a difference in currency and security handling. So we just
+ // preload the object right here.
+ d->m_cache.preloadSecurity(currency);
+}
+
+void MyMoneyFile::modifyCurrency(const MyMoneySecurity& currency)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ // force reload of base currency object
+ if(currency.id() == d->m_baseCurrency.id())
+ d->m_baseCurrency.clearId();
+
+ m_storage->modifyCurrency(currency);
+
+ addNotification(currency.id());
+}
+
+void MyMoneyFile::removeCurrency(const MyMoneySecurity& currency)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ if(currency.id() == d->m_baseCurrency.id()) {
+ throw new MYMONEYEXCEPTION("Cannot delete base currency.");
+ }
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeCurrency(currency);
+
+ addNotification(currency.id(), false);
+}
+
+const MyMoneySecurity& MyMoneyFile::currency(const QString& id) const
+{
+ if(id.isEmpty())
+ return baseCurrency();
+
+ const MyMoneySecurity& curr = d->m_cache.security(id);
+ if(curr.id().isEmpty())
+ throw new MYMONEYEXCEPTION("Currency not found.");
+ return curr;
+}
+
+const QValueList<MyMoneySecurity> MyMoneyFile::currencyList(void) const
+{
+ checkStorage();
+
+ return m_storage->currencyList();
+}
+
+const QString& MyMoneyFile::foreignCurrency(const QString& first, const QString& second) const
+{
+ if(baseCurrency().id() == second)
+ return first;
+ return second;
+}
+
+const MyMoneySecurity& MyMoneyFile::baseCurrency(void) const
+{
+ if(d->m_baseCurrency.id().isEmpty()) {
+ QString id = QString(value("kmm-baseCurrency"));
+ if(!id.isEmpty())
+ d->m_baseCurrency = currency(id);
+ }
+
+ return d->m_baseCurrency;
+}
+
+void MyMoneyFile::setBaseCurrency(const MyMoneySecurity& curr)
+{
+ // make sure the currency exists
+ MyMoneySecurity c = currency(curr.id());
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ if(c.id() != d->m_baseCurrency.id()) {
+ setValue("kmm-baseCurrency", curr.id());
+ // force reload of base currency cache
+ d->m_baseCurrency = MyMoneySecurity();
+ }
+}
+
+void MyMoneyFile::addPrice(const MyMoneyPrice& price)
+{
+ if(price.rate(QString()).isZero())
+ return;
+
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addPrice(price);
+}
+
+void MyMoneyFile::removePrice(const MyMoneyPrice& price)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removePrice(price);
+}
+
+const MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const
+{
+ checkStorage();
+
+ QString to(toId);
+ if(to.isEmpty())
+ to = value("kmm-baseCurrency");
+ // if some id is missing, we can return an empty price object
+ if(fromId.isEmpty() || to.isEmpty())
+ return MyMoneyPrice();
+
+ // we don't search our tables if someone asks stupid stuff
+ if(fromId == toId) {
+ return MyMoneyPrice(fromId, toId, date, MyMoneyMoney(1,1), "KMyMoney");
+ }
+
+ // search 'from-to' rate
+ MyMoneyPrice rc = m_storage->price(fromId, to, date, exactDate);
+ if(!rc.isValid()) {
+ // not found, search 'to-fron' rate and use reciprocal value
+ rc = m_storage->price(to, fromId, date, exactDate);
+ }
+ return rc;
+}
+
+const MyMoneyPriceList MyMoneyFile::priceList(void) const
+{
+ checkStorage();
+
+ return m_storage->priceList();
+}
+
+bool MyMoneyFile::hasAccount(const QString& id, const QString& name) const
+{
+ MyMoneyAccount acc = d->m_cache.account(id);
+ QStringList list = acc.accountList();
+ QStringList::ConstIterator it;
+ bool rc = false;
+ for(it = list.begin(); rc == false && it != list.end(); ++it) {
+ MyMoneyAccount a = d->m_cache.account(*it);
+ if(a.name() == name)
+ rc = true;
+ }
+ return rc;
+}
+
+const QValueList<MyMoneyReport> MyMoneyFile::reportList( void ) const
+{
+ checkStorage();
+
+ return m_storage->reportList();
+}
+
+void MyMoneyFile::addReport( MyMoneyReport& report )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addReport( report );
+}
+
+void MyMoneyFile::modifyReport( const MyMoneyReport& report )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifyReport( report );
+
+ addNotification(report.id());
+}
+
+unsigned MyMoneyFile::countReports(void) const
+{
+ checkStorage();
+
+ return m_storage->countReports();
+}
+
+const MyMoneyReport MyMoneyFile::report( const QString& id ) const
+{
+ checkStorage();
+
+ return m_storage->report(id);
+}
+
+void MyMoneyFile::removeReport(const MyMoneyReport& report)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeReport(report);
+
+ addNotification(report.id(), false);
+}
+
+
+const QValueList<MyMoneyBudget> MyMoneyFile::budgetList( void ) const
+{
+ checkStorage();
+
+ return m_storage->budgetList();
+}
+
+void MyMoneyFile::addBudget( MyMoneyBudget& budget )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->addBudget( budget );
+}
+
+const MyMoneyBudget MyMoneyFile::budgetByName(const QString& name) const
+{
+ checkStorage();
+
+ return m_storage->budgetByName(name);
+}
+
+void MyMoneyFile::modifyBudget( const MyMoneyBudget& budget )
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+
+ // clear all changed objects from cache
+ MyMoneyNotifier notifier(this);
+
+ m_storage->modifyBudget( budget );
+
+ addNotification(budget.id());
+}
+
+unsigned MyMoneyFile::countBudgets(void) const
+{
+ checkStorage();
+
+ return m_storage->countBudgets();
+}
+
+const MyMoneyBudget MyMoneyFile::budget( const QString& id ) const
+{
+ checkStorage();
+
+ return m_storage->budget(id);
+}
+
+void MyMoneyFile::removeBudget(const MyMoneyBudget& budget)
+{
+ checkTransaction(__PRETTY_FUNCTION__);
+ MyMoneyNotifier notifier(this);
+
+ m_storage->removeBudget(budget);
+
+ addNotification(budget.id(), false);
+}
+
+
+
+bool MyMoneyFile::isReferenced(const MyMoneyObject& obj, const MyMoneyFileBitArray& skipChecks) const
+{
+ checkStorage();
+ return m_storage->isReferenced(obj, skipChecks);
+}
+
+bool MyMoneyFile::checkNoUsed(const QString& accId, const QString& no) const
+{
+ // by definition, an empty string or a non-numeric string is not used
+ QRegExp exp(QString("(.*\\D)?(\\d+)(\\D.*)?"));
+ if(no.isEmpty() || exp.search(no) == -1)
+ return false;
+
+ MyMoneyTransactionFilter filter;
+ filter.addAccount(accId);
+ QValueList<MyMoneyTransaction> transactions = transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+ while ( it_t != transactions.end() ) {
+ try {
+ MyMoneySplit split;
+ // Test whether the transaction also includes a split into
+ // this account
+ split = (*it_t).splitByAccount(accId, true /*match*/);
+ if(!split.number().isEmpty() && split.number() == no)
+ return true;
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+ ++it_t;
+ }
+ return false;
+}
+
+QString MyMoneyFile::highestCheckNo(const QString& accId) const
+{
+ unsigned64 lno = 0, cno;
+ QString no;
+ MyMoneyTransactionFilter filter;
+ filter.addAccount(accId);
+ QValueList<MyMoneyTransaction> transactions = transactionList(filter);
+ QValueList<MyMoneyTransaction>::const_iterator it_t = transactions.begin();
+ while ( it_t != transactions.end() ) {
+ try {
+ // Test whether the transaction also includes a split into
+ // this account
+ MyMoneySplit split = (*it_t).splitByAccount(accId, true /*match*/);
+ if(!split.number().isEmpty()) {
+ // non-numerical values stored in number will return 0 in the next line
+ cno = split.number().toULongLong();
+ if(cno > lno) {
+ lno = cno;
+ no = split.number();
+ }
+ }
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+ ++it_t;
+ }
+ return no;
+}
+
+void MyMoneyFile::clearCache(void)
+{
+ checkStorage();
+ m_storage->clearCache();
+ d->m_cache.clear();
+}
+
+void MyMoneyFile::preloadCache(void)
+{
+ checkStorage();
+
+ d->m_cache.clear();
+ QValueList<MyMoneyAccount> a_list;
+ m_storage->accountList(a_list);
+ d->m_cache.preloadAccount(a_list);
+ d->m_cache.preloadPayee(m_storage->payeeList());
+ d->m_cache.preloadInstitution(m_storage->institutionList());
+ d->m_cache.preloadSecurity(m_storage->securityList() + m_storage->currencyList());
+ d->m_cache.preloadSchedule(m_storage->scheduleList());
+}
+
+bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const
+{
+ bool rc = false;
+ if(t.splitCount() == 2) {
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
+ MyMoneyAccount acc = account((*it_s).accountId());
+ if(acc.isIncomeExpense())
+ break;
+ }
+ if(it_s == t.splits().end())
+ rc = true;
+ }
+ return rc;
+}
+
+bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const
+{
+ QValueList<MyMoneySplit>::const_iterator it_s;
+ const QValueList<MyMoneySplit>& list = t.splits();
+ for(it_s = list.begin(); it_s != list.end(); ++it_s) {
+ if(referencesClosedAccount(*it_s))
+ break;
+ }
+ return it_s != list.end();
+}
+
+bool MyMoneyFile::referencesClosedAccount(const MyMoneySplit& s) const
+{
+ if(s.accountId().isEmpty())
+ return false;
+
+ try {
+ return account(s.accountId()).isClosed();
+ } catch(MyMoneyException *e) {
+ delete e;
+ }
+ return false;
+}
+
+MyMoneyFileTransaction::MyMoneyFileTransaction() :
+ m_isNested(MyMoneyFile::instance()->hasTransaction()),
+ m_needRollback(!m_isNested)
+{
+ if(!m_isNested)
+ MyMoneyFile::instance()->startTransaction();
+}
+
+MyMoneyFileTransaction::~MyMoneyFileTransaction()
+{
+ rollback();
+}
+
+void MyMoneyFileTransaction::restart(void)
+{
+ rollback();
+
+ m_needRollback = !m_isNested;
+ if(!m_isNested)
+ MyMoneyFile::instance()->startTransaction();
+}
+
+void MyMoneyFileTransaction::commit(void)
+{
+ if(!m_isNested)
+ MyMoneyFile::instance()->commitTransaction();
+ m_needRollback = false;
+}
+
+void MyMoneyFileTransaction::rollback(void)
+{
+ if(m_needRollback)
+ MyMoneyFile::instance()->rollbackTransaction();
+ m_needRollback = false;
+}
+
+
+#include "mymoneyfile.moc"