/*************************************************************************** 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 #endif // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // 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 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 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::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 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::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::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::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 transactions = transactionList(filter); TQValueList::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(TQString("Unable to create opening balance account for security %1").arg(security.id())); 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.utf8()))); TQValueList accounts; TQValueList::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.utf8())); 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::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 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& 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::Iterator it; TQValueList::Iterator next; for(it = list.begin(); it != list.end(); ) { if(isStandardAccount( (*it).id() )) { it = list.erase(it); } else { ++it; } } } else { TQValueList::ConstIterator it; TQValueList 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& list) const { d->m_cache.institution(list); } const TQValueList MyMoneyFile::institutionList(void) const { TQValueList 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 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(TQString("Missing price info for conversion from %1 to %2").arg(from.name()).arg(to.name())); } catch(MyMoneyException *e) { tqFatal(TQString("Missing security caught in MyMoneyFile::warningMissingRate(): %1(%2) %3").arg(e->file()).arg(e->line()).arg(e->what())); delete e; } } void MyMoneyFile::notify(void) { TQMap::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 >& list, MyMoneyTransactionFilter& filter) const { checkStorage(); m_storage->transactionList(list, filter); } void MyMoneyFile::transactionList(TQValueList& list, MyMoneyTransactionFilter& filter) const { checkStorage(); m_storage->transactionList(list, filter); } const TQValueList MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const { TQValueList list; transactionList(list, filter); return list; } const TQValueList MyMoneyFile::payeeList(void) const { TQValueList 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::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::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 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 list; TQValueList::Iterator it_a; TQValueList::Iterator it_sch; TQValueList::Iterator it_p; TQValueList::Iterator it_t; TQValueList::Iterator it_r; TQStringList accountRebuild; TQStringList::ConstIterator it_c; TQMap 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::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(&(*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::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 pList = payeeList(); TQMappayeeConversionMap; 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 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::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::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 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::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 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::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(TQString("Unable to add account %1, %2, %3: %4"). arg(categoryAccount.name()). arg(parent.name()). arg(categoryText). arg(e->what())); delete e; } parent = categoryAccount; } return categoryToAccount(name); } const TQValueList 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 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 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 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 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 transactions = transactionList(filter); TQValueList::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 transactions = transactionList(filter); TQValueList::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 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::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::const_iterator it_s; const TQValueList& 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"