You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/kmymoney2/mymoney/mymoneyfile.cpp

2335 lines
67 KiB

/***************************************************************************
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. *
* *
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
// ----------------------------------------------------------------------------
// QT Includes
#include <tqstring.h>
#include <tqdatetime.h>
#include <tqvaluelist.h>
// ----------------------------------------------------------------------------
// KDE Includes
#include <kdebug.h>
#include <tdelocale.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"
#endif
const TQString MyMoneyFile::OpeningBalancesPrefix = I18N_NOOP("Opening Balances");
const TQString 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
*/
TQMap<TQString, 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(TQString("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;
TQValueList<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;
TQValueList<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 TQString& id) const
{
return d->m_cache.institution(id);
}
const MyMoneyAccount& MyMoneyFile::account(const TQString& id) const
{
return d->m_cache.account(id);
}
const MyMoneyAccount& MyMoneyFile::subAccountByName(const MyMoneyAccount& acc, const TQString& name) const
{
static MyMoneyAccount nullAccount;
TQValueList<TQString>::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 TQString& 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());
TQValueList<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 TQString& id) const
{
checkStorage();
return m_storage->hasActiveSplits(id);
}
bool MyMoneyFile::isStandardAccount(const TQString& id) const
{
checkStorage();
return m_storage->isStandardAccount(id);
}
void MyMoneyFile::setAccountName(const TQString& id, const TQString& 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
TQStringList::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(TQString());
m_storage->removeAccount(acc);
addNotification(acc.id(), false);
d->m_cache.clear(acc.id());
}
void MyMoneyFile::removeAccountList(const TQStringList& 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 (TQStringList::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 TQStringList& 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 (TQStringList::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);
TQValueList<TQString>::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(TQString());
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
TQStringList::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(TQDate::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;
}
TQString MyMoneyFile::openingBalanceTransaction(const MyMoneyAccount& acc) const
{
TQString 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());
TQValueList<MyMoneyTransaction> transactions = transactionList(filter);
TQValueList<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) {
tqDebug("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;
TQRegExp match(TQString("^%1").arg(i18n(MyMoneyFile::OpeningBalancesPrefix)));
TQValueList<MyMoneyAccount> accounts;
TQValueList<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(TQString("No opening balance account for %1").arg(security.tradingSymbol()));
}
return acc;
}
const MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security)
{
checkTransaction(__PRETTY_FUNCTION__);
MyMoneyAccount acc;
TQString name(i18n(MyMoneyFile::OpeningBalancesPrefix));
if(security.id() != baseCurrency().id()) {
name += TQString(" (%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;
TQValueList<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) {
TQValueList<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 TQString& id) const
{
checkStorage();
return m_storage->transaction(id);
}
const MyMoneyTransaction MyMoneyFile::transaction(const TQString& 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 TQString& id) const
{
return d->m_cache.payee(id);
}
const MyMoneyPayee& MyMoneyFile::payeeByName(const TQString& 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(TQValueList<MyMoneyAccount>& list, const TQStringList& 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
TQValueList<MyMoneyAccount>::Iterator it;
TQValueList<MyMoneyAccount>::Iterator next;
for(it = list.begin(); it != list.end(); ) {
if(isStandardAccount( (*it).id() )) {
it = list.erase(it);
} else {
++it;
}
}
} else {
TQValueList<MyMoneyAccount>::ConstIterator it;
TQValueList<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(TQValueList<MyMoneyInstitution>& list) const
{
d->m_cache.institution(list);
}
const TQValueList<MyMoneyInstitution> MyMoneyFile::institutionList(void) const
{
TQValueList<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 TQString& account) const
{
checkStorage();
return m_storage->transactionCount(account);
}
const TQMap<TQString, 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 TQString& id, const TQDate& date) const
{
checkStorage();
return m_storage->balance(id, date);
}
const MyMoneyMoney MyMoneyFile::totalBalance(const TQString& id, const TQDate& date) const
{
checkStorage();
return m_storage->totalBalance(id, date);
}
void MyMoneyFile::warningMissingRate(const TQString& fromId, const TQString& toId) const
{
MyMoneySecurity from, to;
try {
from = security(fromId);
to = security(toId);
tqWarning("Missing price info for conversion from %s to %s", from.name().latin1(), to.name().latin1());
} catch(MyMoneyException *e) {
tqFatal("Missing security caught in MyMoneyFile::warningMissingRate(): %s(%ld) %s", e->file().data(), e->line(), e->what().data());
delete e;
}
}
void MyMoneyFile::notify(void)
{
TQMap<TQString, 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 TQString& 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(TQValueList<TQPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const
{
checkStorage();
m_storage->transactionList(list, filter);
}
void MyMoneyFile::transactionList(TQValueList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const
{
checkStorage();
m_storage->transactionList(list, filter);
}
const TQValueList<MyMoneyTransaction> MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const
{
TQValueList<MyMoneyTransaction> list;
transactionList(list, filter);
return list;
}
const TQValueList<MyMoneyPayee> MyMoneyFile::payeeList(void) const
{
TQValueList<MyMoneyPayee> list;
d->m_cache.payee(list);
return list;
}
TQString MyMoneyFile::accountToCategory(const TQString& accountId, bool includeStandardAccounts) const
{
MyMoneyAccount acc;
TQString 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;
}
TQString MyMoneyFile::categoryToAccount(const TQString& category, MyMoneyAccount::accountTypeE type) const
{
TQString 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;
}
TQString MyMoneyFile::nameToAccount(const TQString& name) const
{
TQString 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;
}
TQString MyMoneyFile::parentName(const TQString& name) const
{
return name.section(AccountSeperator, 0, -2);
}
TQString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const TQString& category) const
{
MyMoneyAccount nextBase;
TQString level, remainder;
level = category.section(AccountSeperator, 0, 0);
remainder = category.section(AccountSeperator, 1);
TQStringList list = base.accountList();
TQStringList::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 TQString::null;
}
TQString MyMoneyFile::value(const TQString& key) const
{
checkStorage();
return m_storage->value(key);
}
void MyMoneyFile::setValue(const TQString& key, const TQString& val)
{
checkTransaction(__PRETTY_FUNCTION__);
// clear all changed objects from cache
MyMoneyNotifier notifier(this);
m_storage->setValue(key, val);
}
void MyMoneyFile::deletePair(const TQString& 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();
TQValueList<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();
TQValueList<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 TQString& id) const
{
return d->m_cache.schedule(id);
}
const TQValueList<MyMoneySchedule> MyMoneyFile::scheduleList(
const TQString& accountId,
const MyMoneySchedule::typeE type,
const MyMoneySchedule::occurenceE occurence,
const MyMoneySchedule::paymentTypeE paymentType,
const TQDate& startDate,
const TQDate& endDate,
const bool overdue) const
{
checkStorage();
return m_storage->scheduleList(accountId, type, occurence, paymentType, startDate, endDate, overdue);
}
const TQStringList MyMoneyFile::consistencyCheck(void)
{
TQValueList<MyMoneyAccount> list;
TQValueList<MyMoneyAccount>::Iterator it_a;
TQValueList<MyMoneySchedule>::Iterator it_sch;
TQValueList<MyMoneyPayee>::Iterator it_p;
TQValueList<MyMoneyTransaction>::Iterator it_t;
TQValueList<MyMoneyReport>::Iterator it_r;
TQStringList accountRebuild;
TQStringList::ConstIterator it_c;
TQMap<TQString, bool> interestAccounts;
MyMoneyAccount parent;
MyMoneyAccount child;
MyMoneyAccount toplevel;
TQString parentId;
TQStringList rc;
int problemCount = 0;
int unfixedCount = 0;
TQString 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:
tqWarning("%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++;
TQValueList<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 << TQString(" %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) {
TQValueList<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
TQValueList<MyMoneyPayee> pList = payeeList();
TQMap<TQString, TQString>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
TQValueList<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);
TQValueList<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);
TQValueList<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
TQValueList<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;
TQValueList<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(TQString());
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
TQValueList<MyMoneyReport> rList = reportList();
for(it_r = rList.begin(); it_r != rList.end(); ++it_r) {
MyMoneyReport r = *it_r;
TQStringList pList;
TQStringList::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
TQMap<TQString, TQString>::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;
}
TQString MyMoneyFile::createCategory(const MyMoneyAccount& base, const TQString& name)
{
checkTransaction(__PRETTY_FUNCTION__);
MyMoneyAccount parent = base;
TQString categoryText;
if(base.id() != expense().id() && base.id() != income().id())
throw new MYMONEYEXCEPTION("Invalid base category");
TQStringList subAccounts = TQStringList::split(AccountSeperator, name);
TQStringList::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
{
TQString categoryId = categoryToAccount(categoryText);
if (categoryId.isEmpty())
addAccount(categoryAccount, parent);
else
{
categoryAccount = account(categoryId);
}
}
catch (MyMoneyException *e)
{
tqDebug("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 TQValueList<MyMoneySchedule> MyMoneyFile::scheduleListEx( int scheduleTypes,
int scheduleOcurrences,
int schedulePaymentTypes,
TQDate startDate,
const TQStringList& 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 TQString& id) const
{
if(id.isEmpty())
return baseCurrency();
return d->m_cache.security(id);
}
const TQValueList<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 TQString& 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 TQValueList<MyMoneySecurity> MyMoneyFile::currencyList(void) const
{
checkStorage();
return m_storage->currencyList();
}
const TQString& MyMoneyFile::foreignCurrency(const TQString& first, const TQString& second) const
{
if(baseCurrency().id() == second)
return first;
return second;
}
const MyMoneySecurity& MyMoneyFile::baseCurrency(void) const
{
if(d->m_baseCurrency.id().isEmpty()) {
TQString id = TQString(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(TQString()).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 TQString& fromId, const TQString& toId, const TQDate& date, const bool exactDate) const
{
checkStorage();
TQString 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 TQString& id, const TQString& name) const
{
MyMoneyAccount acc = d->m_cache.account(id);
TQStringList list = acc.accountList();
TQStringList::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 TQValueList<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 TQString& 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 TQValueList<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 TQString& 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 TQString& 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 TQString& accId, const TQString& no) const
{
// by definition, an empty string or a non-numeric string is not used
TQRegExp exp(TQString("(.*\\D)?(\\d+)(\\D.*)?"));
if(no.isEmpty() || exp.search(no) == -1)
return false;
MyMoneyTransactionFilter filter;
filter.addAccount(accId);
TQValueList<MyMoneyTransaction> transactions = transactionList(filter);
TQValueList<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;
}
TQString MyMoneyFile::highestCheckNo(const TQString& accId) const
{
unsigned64 lno = 0, cno;
TQString no;
MyMoneyTransactionFilter filter;
filter.addAccount(accId);
TQValueList<MyMoneyTransaction> transactions = transactionList(filter);
TQValueList<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();
TQValueList<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) {
TQValueList<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
{
TQValueList<MyMoneySplit>::const_iterator it_s;
const TQValueList<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"