File indexing completed on 2024-05-12 16:42:36
0001 /* 0002 SPDX-FileCopyrightText: 2000-2003 Michael Edwardes <mte@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2001-2002 Felix Rodriguez <frodriguez@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2002-2004 Kevin Tambascio <ktambascio@users.sourceforge.net> 0005 SPDX-FileCopyrightText: 2004-2005 Ace Jones <acejones@users.sourceforge.net> 0006 SPDX-FileCopyrightText: 2006-2019 Thomas Baumgart <tbaumgart@kde.org> 0007 SPDX-FileCopyrightText: 2006 Darren Gould <darren_gould@gmx.de> 0008 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "mymoneyfile.h" 0013 0014 #include <utility> 0015 0016 // ---------------------------------------------------------------------------- 0017 // QT Includes 0018 0019 #include <QString> 0020 #include <QList> 0021 #include <QUuid> 0022 #include <QLocale> 0023 #include <QBitArray> 0024 #include <QDebug> 0025 0026 // ---------------------------------------------------------------------------- 0027 // KDE Includes 0028 0029 #include <KLocalizedString> 0030 0031 // ---------------------------------------------------------------------------- 0032 // Project Includes 0033 0034 #include "mymoneystoragemgr.h" 0035 #include "mymoneyinstitution.h" 0036 #include "mymoneyaccount.h" 0037 #include "mymoneyaccountloan.h" 0038 #include "mymoneysecurity.h" 0039 #include "mymoneyreport.h" 0040 #include "mymoneybalancecache.h" 0041 #include "mymoneybudget.h" 0042 #include "mymoneyprice.h" 0043 #include "mymoneypayee.h" 0044 #include "mymoneytag.h" 0045 #include "mymoneyschedule.h" 0046 #include "mymoneysplit.h" 0047 #include "mymoneytransaction.h" 0048 #include "mymoneycostcenter.h" 0049 #include "mymoneyexception.h" 0050 #include "onlinejob.h" 0051 #include "storageenums.h" 0052 #include "mymoneyenums.h" 0053 0054 // include the following line to get a 'cout' for debug purposes 0055 // #include <iostream> 0056 0057 using namespace eMyMoney; 0058 0059 const QString MyMoneyFile::AccountSeparator = QChar(':'); 0060 0061 MyMoneyFile MyMoneyFile::file; 0062 0063 typedef QList<std::pair<QString, QDate> > BalanceNotifyList; 0064 typedef QMap<QString, bool> CacheNotifyList; 0065 0066 /// @todo make this template based 0067 class MyMoneyNotification 0068 { 0069 public: 0070 MyMoneyNotification(File::Mode mode, const MyMoneyTransaction& t) : 0071 m_objType(File::Object::Transaction), 0072 m_notificationMode(mode), 0073 m_id(t.id()) { 0074 } 0075 0076 MyMoneyNotification(File::Mode mode, const MyMoneyAccount& acc) : 0077 m_objType(File::Object::Account), 0078 m_notificationMode(mode), 0079 m_id(acc.id()) { 0080 } 0081 0082 MyMoneyNotification(File::Mode mode, const MyMoneyInstitution& institution) : 0083 m_objType(File::Object::Institution), 0084 m_notificationMode(mode), 0085 m_id(institution.id()) { 0086 } 0087 0088 MyMoneyNotification(File::Mode mode, const MyMoneyPayee& payee) : 0089 m_objType(File::Object::Payee), 0090 m_notificationMode(mode), 0091 m_id(payee.id()) { 0092 } 0093 0094 MyMoneyNotification(File::Mode mode, const MyMoneyTag& tag) : 0095 m_objType(File::Object::Tag), 0096 m_notificationMode(mode), 0097 m_id(tag.id()) { 0098 } 0099 0100 MyMoneyNotification(File::Mode mode, const MyMoneySchedule& schedule) : 0101 m_objType(File::Object::Schedule), 0102 m_notificationMode(mode), 0103 m_id(schedule.id()) { 0104 } 0105 0106 MyMoneyNotification(File::Mode mode, const MyMoneySecurity& security) : 0107 m_objType(File::Object::Security), 0108 m_notificationMode(mode), 0109 m_id(security.id()) { 0110 } 0111 0112 MyMoneyNotification(File::Mode mode, const onlineJob& job) : 0113 m_objType(File::Object::OnlineJob), 0114 m_notificationMode(mode), 0115 m_id(job.id()) { 0116 } 0117 0118 File::Object objectType() const { 0119 return m_objType; 0120 } 0121 File::Mode notificationMode() const { 0122 return m_notificationMode; 0123 } 0124 const QString& id() const { 0125 return m_id; 0126 } 0127 0128 protected: 0129 MyMoneyNotification(File::Object obj, 0130 File::Mode mode, 0131 const QString& id) : 0132 m_objType(obj), 0133 m_notificationMode(mode), 0134 m_id(id) {} 0135 0136 private: 0137 File::Object m_objType; 0138 File::Mode m_notificationMode; 0139 QString m_id; 0140 }; 0141 0142 0143 0144 0145 0146 class MyMoneyFile::Private 0147 { 0148 public: 0149 Private() : 0150 m_storage(0), 0151 m_inTransaction(false) {} 0152 0153 ~Private() { 0154 delete m_storage; 0155 } 0156 /** 0157 * This method is used to add an id to the list of objects 0158 * to be removed from the cache. If id is empty, then nothing is added to the list. 0159 * 0160 * @param id id of object to be notified 0161 * @param reload reload the object (@c true) or not (@c false). The default is @c true 0162 * @see attach, detach 0163 */ 0164 void addCacheNotification(const QString& id, const QDate& date) { 0165 if (!id.isEmpty()) 0166 m_balanceNotifyList.append(std::make_pair(id, date)); 0167 } 0168 0169 /** 0170 * This method is used to clear the notification list 0171 */ 0172 void clearCacheNotification() { 0173 // reset list to be empty 0174 m_balanceNotifyList.clear(); 0175 } 0176 0177 /** 0178 * This method is used to clear all 0179 * objects mentioned in m_notificationList from the cache. 0180 */ 0181 void notify() { 0182 foreach (const BalanceNotifyList::value_type & i, m_balanceNotifyList) { 0183 m_balanceChangedSet += i.first; 0184 if (i.second.isValid()) { 0185 m_balanceCache.clear(i.first, i.second); 0186 } else { 0187 m_balanceCache.clear(i.first); 0188 } 0189 } 0190 0191 clearCacheNotification(); 0192 } 0193 0194 /** 0195 * This method checks if a storage object is attached and 0196 * throws and exception if not. 0197 */ 0198 inline void checkStorage() const { 0199 if (m_storage == 0) 0200 throw MYMONEYEXCEPTION_CSTRING("No storage object attached to MyMoneyFile"); 0201 } 0202 0203 /** 0204 * This method checks that a transaction has been started with 0205 * startTransaction() and throws an exception otherwise. Calls 0206 * checkStorage() to make sure a storage object is present and attached. 0207 */ 0208 void checkTransaction(const char* txt) const { 0209 checkStorage(); 0210 if (!m_inTransaction) 0211 throw MYMONEYEXCEPTION(QString::fromLatin1("No transaction started for %1").arg(QString::fromLatin1(txt))); 0212 } 0213 0214 void priceChanged(const MyMoneyFile& file, const MyMoneyPrice price) { 0215 // get all affected accounts and add them to the m_valueChangedSet 0216 QList<MyMoneyAccount> accList; 0217 file.accountList(accList); 0218 QList<MyMoneyAccount>::const_iterator account_it; 0219 for (account_it = accList.constBegin(); account_it != accList.constEnd(); ++account_it) { 0220 QString currencyId = account_it->currencyId(); 0221 if (currencyId != file.baseCurrency().id() && (currencyId == price.from() || currencyId == price.to())) { 0222 // this account is not in the base currency and the price affects it's value 0223 m_valueChangedSet.insert(account_it->id()); 0224 } 0225 } 0226 } 0227 0228 /** 0229 * This member points to the storage strategy 0230 */ 0231 MyMoneyStorageMgr *m_storage; 0232 0233 0234 bool m_inTransaction; 0235 MyMoneySecurity m_baseCurrency; 0236 0237 /** 0238 * @brief Cache for MyMoneyObjects 0239 * 0240 * It is also used to emit the objectAdded() and objectModified() signals. 0241 * => If one of these signals is used, you must use this cache. 0242 */ 0243 MyMoneyPriceList m_priceCache; 0244 MyMoneyBalanceCache m_balanceCache; 0245 0246 /** 0247 * This member keeps a list of account ids to notify 0248 * after a single operation is completed. The balance cache 0249 * is cleared for that account and all dates on or after 0250 * the one supplied. If the date is invalid, the entire 0251 * balance cache is cleared for that account. 0252 */ 0253 BalanceNotifyList m_balanceNotifyList; 0254 0255 /** 0256 * This member keeps a list of account ids for which 0257 * a balanceChanged() signal needs to be emitted when 0258 * a set of operations has been committed. 0259 * 0260 * @sa MyMoneyFile::commitTransaction() 0261 */ 0262 QSet<QString> m_balanceChangedSet; 0263 0264 /** 0265 * This member keeps a list of account ids for which 0266 * a valueChanged() signal needs to be emitted when 0267 * a set of operations has been committed. 0268 * 0269 * @sa MyMoneyFile::commitTransaction() 0270 */ 0271 QSet<QString> m_valueChangedSet; 0272 0273 /** 0274 * This member keeps the list of changes in the engine 0275 * in historical order. The type can be 'added', 'modified' 0276 * or removed. 0277 */ 0278 QList<MyMoneyNotification> m_changeSet; 0279 }; 0280 0281 0282 class MyMoneyNotifier 0283 { 0284 public: 0285 MyMoneyNotifier(MyMoneyFile::Private* file) { 0286 m_file = file; 0287 m_file->clearCacheNotification(); 0288 } 0289 ~MyMoneyNotifier() { 0290 m_file->notify(); 0291 } 0292 private: 0293 MyMoneyFile::Private* m_file; 0294 }; 0295 0296 0297 0298 MyMoneyFile::MyMoneyFile() : 0299 d(new Private) 0300 { 0301 } 0302 0303 MyMoneyFile::~MyMoneyFile() 0304 { 0305 delete d; 0306 } 0307 0308 MyMoneyFile::MyMoneyFile(MyMoneyStorageMgr *storage) : 0309 d(new Private) 0310 { 0311 attachStorage(storage); 0312 } 0313 0314 MyMoneyFile* MyMoneyFile::instance() 0315 { 0316 return &file; 0317 } 0318 0319 void MyMoneyFile::attachStorage(MyMoneyStorageMgr* const storage) 0320 { 0321 if (d->m_storage != 0) 0322 throw MYMONEYEXCEPTION_CSTRING("Storage already attached"); 0323 0324 if (storage == 0) 0325 throw MYMONEYEXCEPTION_CSTRING("Storage must not be 0"); 0326 0327 d->m_storage = storage; 0328 0329 // force reload of base currency 0330 d->m_baseCurrency = MyMoneySecurity(); 0331 0332 // and the whole cache 0333 d->m_balanceCache.clear(); 0334 d->m_priceCache.clear(); 0335 0336 // notify application about new data availability 0337 emit beginChangeNotification(); 0338 emit dataChanged(); 0339 emit endChangeNotification(); 0340 } 0341 0342 void MyMoneyFile::detachStorage(MyMoneyStorageMgr* const /* storage */) 0343 { 0344 d->m_balanceCache.clear(); 0345 d->m_priceCache.clear(); 0346 d->m_storage = nullptr; 0347 } 0348 0349 MyMoneyStorageMgr* MyMoneyFile::storage() const 0350 { 0351 return d->m_storage; 0352 } 0353 0354 bool MyMoneyFile::storageAttached() const 0355 { 0356 return d->m_storage != 0; 0357 } 0358 0359 void MyMoneyFile::startTransaction() 0360 { 0361 d->checkStorage(); 0362 if (d->m_inTransaction) { 0363 throw MYMONEYEXCEPTION_CSTRING("Already started a transaction!"); 0364 } 0365 0366 d->m_storage->startTransaction(); 0367 d->m_inTransaction = true; 0368 d->m_changeSet.clear(); 0369 } 0370 0371 bool MyMoneyFile::hasTransaction() const 0372 { 0373 return d->m_inTransaction; 0374 } 0375 0376 void MyMoneyFile::commitTransaction() 0377 { 0378 d->checkTransaction(Q_FUNC_INFO); 0379 0380 // commit the transaction in the storage 0381 const auto changed = d->m_storage->commitTransaction(); 0382 d->m_inTransaction = false; 0383 0384 // collect notifications about removed objects 0385 QStringList removedObjects; 0386 const auto& set = d->m_changeSet; 0387 for (const auto& change : set) { 0388 switch (change.notificationMode()) { 0389 case File::Mode::Remove: 0390 removedObjects += change.id(); 0391 break; 0392 default: 0393 break; 0394 } 0395 } 0396 0397 // inform the outside world about the beginning of notifications 0398 emit beginChangeNotification(); 0399 0400 // Now it's time to send out some signals to the outside world 0401 // First we go through the d->m_changeSet and emit respective 0402 // signals about addition, modification and removal of engine objects 0403 const auto& changes = d->m_changeSet; 0404 for (const auto& change : changes) { 0405 switch (change.notificationMode()) { 0406 case File::Mode::Remove: 0407 emit objectRemoved(change.objectType(), change.id()); 0408 // if there is a balance change recorded for this account remove it since the account itself will be removed 0409 // this can happen when deleting categories that have transactions and the reassign category feature was used 0410 d->m_balanceChangedSet.remove(change.id()); 0411 break; 0412 case File::Mode::Add: 0413 if (!removedObjects.contains(change.id())) { 0414 emit objectAdded(change.objectType(), change.id()); 0415 } 0416 break; 0417 case File::Mode::Modify: 0418 if (!removedObjects.contains(change.id())) { 0419 emit objectModified(change.objectType(), change.id()); 0420 } 0421 break; 0422 } 0423 } 0424 0425 // we're done with the change set, so we clear it 0426 d->m_changeSet.clear(); 0427 0428 // now send out the balanceChanged signal for all those 0429 // accounts for which we have an indication about a possible 0430 // change. 0431 const auto& balanceChanges = d->m_balanceChangedSet; 0432 for (const auto& id : balanceChanges) { 0433 if (!removedObjects.contains(id)) { 0434 // if we notify about balance change we don't need to notify about value change 0435 // for the same account since a balance change implies a value change 0436 d->m_valueChangedSet.remove(id); 0437 emit balanceChanged(account(id)); 0438 } 0439 } 0440 d->m_balanceChangedSet.clear(); 0441 0442 // now notify about the remaining value changes 0443 const auto& m_valueChanges = d->m_valueChangedSet; 0444 for (const auto& id : m_valueChanges) { 0445 if (!removedObjects.contains(id)) { 0446 emit valueChanged(account(id)); 0447 } 0448 } 0449 0450 d->m_valueChangedSet.clear(); 0451 0452 // as a last action, send out the global dataChanged signal 0453 if (changed) 0454 emit dataChanged(); 0455 0456 // inform the outside world about the end of notifications 0457 emit endChangeNotification(); 0458 } 0459 0460 void MyMoneyFile::rollbackTransaction() 0461 { 0462 d->checkTransaction(Q_FUNC_INFO); 0463 0464 d->m_storage->rollbackTransaction(); 0465 d->m_inTransaction = false; 0466 d->m_balanceChangedSet.clear(); 0467 d->m_valueChangedSet.clear(); 0468 d->m_changeSet.clear(); 0469 } 0470 0471 void MyMoneyFile::addInstitution(MyMoneyInstitution& institution) 0472 { 0473 // perform some checks to see that the institution stuff is OK. For 0474 // now we assume that the institution must have a name, the ID is not set 0475 // and it does not have a parent (MyMoneyFile). 0476 0477 if (institution.name().length() == 0 0478 || institution.id().length() != 0) 0479 throw MYMONEYEXCEPTION_CSTRING("Not a new institution"); 0480 0481 d->checkTransaction(Q_FUNC_INFO); 0482 0483 d->m_storage->addInstitution(institution); 0484 d->m_changeSet += MyMoneyNotification(File::Mode::Add, institution); 0485 } 0486 0487 void MyMoneyFile::modifyInstitution(const MyMoneyInstitution& institution) 0488 { 0489 d->checkTransaction(Q_FUNC_INFO); 0490 0491 d->m_storage->modifyInstitution(institution); 0492 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); 0493 } 0494 0495 void MyMoneyFile::modifyTransaction(const MyMoneyTransaction& transaction) 0496 { 0497 d->checkTransaction(Q_FUNC_INFO); 0498 0499 MyMoneyTransaction tCopy(transaction); 0500 0501 // now check the splits 0502 bool loanAccountAffected = false; 0503 const auto splits1 = transaction.splits(); 0504 for (const auto& split : splits1) { 0505 // the following line will throw an exception if the 0506 // account does not exist 0507 auto acc = MyMoneyFile::account(split.accountId()); 0508 if (acc.id().isEmpty()) 0509 throw MYMONEYEXCEPTION_CSTRING("Cannot store split with no account assigned"); 0510 if (isStandardAccount(split.accountId())) 0511 throw MYMONEYEXCEPTION_CSTRING("Cannot store split referencing standard account"); 0512 if (acc.isLoan() && (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))) 0513 loanAccountAffected = true; 0514 } 0515 0516 // change transfer splits between asset/liability and loan accounts 0517 // into amortization splits 0518 if (loanAccountAffected) { 0519 const auto splits = transaction.splits(); 0520 for (const auto& split : splits) { 0521 if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) { 0522 auto acc = MyMoneyFile::account(split.accountId()); 0523 0524 if (acc.isAssetLiability()) { 0525 MyMoneySplit s = split; 0526 s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); 0527 tCopy.modifySplit(s); 0528 } 0529 } 0530 } 0531 } 0532 0533 // clear all changed objects from cache 0534 MyMoneyNotifier notifier(d); 0535 0536 // get the current setting of this transaction 0537 MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id()); 0538 0539 // scan the splits again to update notification list 0540 // and mark all accounts that are referenced 0541 const auto splits2 = tr.splits(); 0542 foreach (const auto& split, splits2) 0543 d->addCacheNotification(split.accountId(), tr.postDate()); 0544 0545 // make sure the value is rounded to the accounts precision 0546 fixSplitPrecision(tCopy); 0547 0548 // perform modification 0549 d->m_storage->modifyTransaction(tCopy); 0550 0551 // and mark all accounts that are referenced 0552 const auto splits3 = tCopy.splits(); 0553 for (const auto& split : splits3) 0554 d->addCacheNotification(split.accountId(), tCopy.postDate()); 0555 0556 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, transaction); 0557 } 0558 0559 void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account) 0560 { 0561 d->checkTransaction(Q_FUNC_INFO); 0562 0563 MyMoneyAccount account(_account); 0564 0565 auto acc = MyMoneyFile::account(account.id()); 0566 0567 // check that for standard accounts only specific parameters are changed 0568 if (isStandardAccount(account.id())) { 0569 // make sure to use the stuff we found on file 0570 account = acc; 0571 0572 // and only use the changes that are allowed 0573 account.setName(_account.name()); 0574 account.setCurrencyId(_account.currencyId()); 0575 0576 // now check that it is the same 0577 if (!(account == _account)) 0578 throw MYMONEYEXCEPTION_CSTRING("Unable to modify the standard account groups"); 0579 } 0580 0581 if (account.accountType() != acc.accountType() // 0582 && !account.isLiquidAsset() && !acc.isLiquidAsset()) 0583 throw MYMONEYEXCEPTION_CSTRING("Unable to change account type"); 0584 0585 // if the account was moved to another institution, we notify 0586 // the old one as well as the new one and the structure change 0587 if (acc.institutionId() != account.institutionId()) { 0588 MyMoneyInstitution inst; 0589 if (!acc.institutionId().isEmpty()) { 0590 inst = institution(acc.institutionId()); 0591 inst.removeAccountId(acc.id()); 0592 modifyInstitution(inst); 0593 // modifyInstitution updates d->m_changeSet already 0594 } 0595 if (!account.institutionId().isEmpty()) { 0596 inst = institution(account.institutionId()); 0597 inst.addAccountId(acc.id()); 0598 modifyInstitution(inst); 0599 // modifyInstitution updates d->m_changeSet already 0600 } 0601 } 0602 0603 // check if account can be closed 0604 if (account.isClosed() && !acc.isClosed()) { 0605 // balance must be zero 0606 if (!account.balance().isZero()) 0607 throw MYMONEYEXCEPTION_CSTRING("Cannot close account with balance unequal to zero"); 0608 if (account.hasOnlineMapping()) 0609 throw MYMONEYEXCEPTION_CSTRING("Cannot close account with active online mapping"); 0610 0611 // all children must be closed already 0612 const auto accList = account.accountList(); 0613 for (const auto& sAccount : accList) { 0614 const auto subAccount = MyMoneyFile::instance()->account(sAccount); 0615 if (!subAccount.isClosed()) { 0616 throw MYMONEYEXCEPTION_CSTRING("Cannot close account with open sub-account"); 0617 } 0618 } 0619 0620 // there must be no unfinished schedule referencing the account 0621 QList<MyMoneySchedule> list = scheduleList(); 0622 QList<MyMoneySchedule>::const_iterator it_l; 0623 for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) { 0624 if ((*it_l).isFinished()) 0625 continue; 0626 if ((*it_l).hasReferenceTo(acc.id())) { 0627 throw MYMONEYEXCEPTION_CSTRING("Cannot close account referenced in schedule"); 0628 } 0629 } 0630 } 0631 0632 d->m_storage->modifyAccount(account); 0633 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, account); 0634 } 0635 0636 void MyMoneyFile::reparentAccount(MyMoneyAccount &acc, MyMoneyAccount& parent) 0637 { 0638 d->checkTransaction(Q_FUNC_INFO); 0639 0640 // check that it's not one of the standard account groups 0641 if (isStandardAccount(acc.id())) 0642 throw MYMONEYEXCEPTION_CSTRING("Unable to reparent the standard account groups"); 0643 0644 if (acc.accountGroup() == parent.accountGroup() 0645 || (acc.accountType() == Account::Type::Income && parent.accountType() == Account::Type::Expense) 0646 || (acc.accountType() == Account::Type::Expense && parent.accountType() == Account::Type::Income)) { 0647 0648 if (acc.isInvest() && parent.accountType() != Account::Type::Investment) 0649 throw MYMONEYEXCEPTION_CSTRING("Unable to reparent Stock to non-investment account"); 0650 0651 if (parent.accountType() == Account::Type::Investment && !acc.isInvest()) 0652 throw MYMONEYEXCEPTION_CSTRING("Unable to reparent non-stock to investment account"); 0653 0654 // keep a notification of the current parent 0655 MyMoneyAccount curParent = account(acc.parentAccountId()); 0656 0657 d->m_storage->reparentAccount(acc, parent); 0658 0659 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, curParent); 0660 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); 0661 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); 0662 0663 } else 0664 throw MYMONEYEXCEPTION_CSTRING("Unable to reparent to different account type"); 0665 } 0666 0667 MyMoneyInstitution MyMoneyFile::institution(const QString& id) const 0668 { 0669 return d->m_storage->institution(id); 0670 } 0671 0672 MyMoneyAccount MyMoneyFile::account(const QString& id) const 0673 { 0674 if (Q_UNLIKELY(id.isEmpty())) // FIXME: Stop requesting accounts with empty id 0675 return MyMoneyAccount(); 0676 0677 return d->m_storage->account(id); 0678 } 0679 0680 MyMoneyAccount MyMoneyFile::subAccountByName(const MyMoneyAccount& account, const QString& name) const 0681 { 0682 static MyMoneyAccount nullAccount; 0683 0684 const auto accounts = account.accountList(); 0685 for (const auto& acc : accounts) { 0686 const auto sacc = MyMoneyFile::account(acc); 0687 if (sacc.name().compare(name) == 0) 0688 return sacc; 0689 } 0690 return nullAccount; 0691 } 0692 0693 MyMoneyAccount MyMoneyFile::accountByName(const QString& name) const 0694 { 0695 try { 0696 return d->m_storage->accountByName(name); 0697 } catch (const MyMoneyException &) { 0698 } 0699 return MyMoneyAccount(); 0700 } 0701 0702 void MyMoneyFile::removeTransaction(const MyMoneyTransaction& transaction) 0703 { 0704 d->checkTransaction(Q_FUNC_INFO); 0705 0706 // clear all changed objects from cache 0707 MyMoneyNotifier notifier(d); 0708 0709 // get the engine's idea about this transaction 0710 MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id()); 0711 0712 // scan the splits again to update notification list 0713 const auto splits = tr.splits(); 0714 for (const auto& split : splits) { 0715 auto acc = account(split.accountId()); 0716 if (acc.isClosed()) 0717 throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot remove transaction that references a closed account.")); 0718 d->addCacheNotification(split.accountId(), tr.postDate()); 0719 //FIXME-ALEX Do I need to add d->addCacheNotification(split.tagList()); ?? 0720 } 0721 0722 d->m_storage->removeTransaction(transaction); 0723 0724 // remove a possible notification of that same object from the changeSet 0725 QList<MyMoneyNotification>::iterator it; 0726 for(it = d->m_changeSet.begin(); it != d->m_changeSet.end();) { 0727 if((*it).id() == transaction.id()) { 0728 it = d->m_changeSet.erase(it); 0729 } else { 0730 ++it; 0731 } 0732 } 0733 0734 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, transaction); 0735 } 0736 0737 0738 bool MyMoneyFile::hasActiveSplits(const QString& id) const 0739 { 0740 d->checkStorage(); 0741 0742 return d->m_storage->hasActiveSplits(id); 0743 } 0744 0745 bool MyMoneyFile::isStandardAccount(const QString& id) const 0746 { 0747 d->checkStorage(); 0748 0749 return d->m_storage->isStandardAccount(id); 0750 } 0751 0752 void MyMoneyFile::setAccountName(const QString& id, const QString& name) const 0753 { 0754 d->checkTransaction(Q_FUNC_INFO); 0755 0756 auto acc = account(id); 0757 d->m_storage->setAccountName(id, name); 0758 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); 0759 } 0760 0761 void MyMoneyFile::removeAccount(const MyMoneyAccount& account) 0762 { 0763 d->checkTransaction(Q_FUNC_INFO); 0764 0765 MyMoneyAccount parent; 0766 MyMoneyAccount acc; 0767 MyMoneyInstitution institution; 0768 0769 // check that the account and its parent exist 0770 // this will throw an exception if the id is unknown 0771 acc = MyMoneyFile::account(account.id()); 0772 parent = MyMoneyFile::account(account.parentAccountId()); 0773 if (!acc.institutionId().isEmpty()) 0774 institution = MyMoneyFile::institution(acc.institutionId()); 0775 0776 // check that it's not one of the standard account groups 0777 if (isStandardAccount(account.id())) 0778 throw MYMONEYEXCEPTION_CSTRING("Unable to remove the standard account groups"); 0779 0780 if (hasActiveSplits(account.id())) { 0781 throw MYMONEYEXCEPTION_CSTRING("Unable to remove account with active splits"); 0782 } 0783 0784 // collect all sub-ordinate accounts for notification 0785 const auto accounts = acc.accountList(); 0786 for (const auto& id : accounts) 0787 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, MyMoneyFile::account(id)); 0788 0789 // don't forget the parent and a possible institution 0790 0791 if (!institution.id().isEmpty()) { 0792 institution.removeAccountId(account.id()); 0793 d->m_storage->modifyInstitution(institution); 0794 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); 0795 } 0796 acc.setInstitutionId(QString()); 0797 0798 d->m_storage->removeAccount(acc); 0799 0800 d->m_balanceCache.clear(acc.id()); 0801 0802 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); 0803 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, acc); 0804 } 0805 0806 void MyMoneyFile::removeAccountList(const QStringList& account_list, unsigned int level) 0807 { 0808 if (level > 100) 0809 throw MYMONEYEXCEPTION_CSTRING("Too deep recursion in [MyMoneyFile::removeAccountList]!"); 0810 0811 d->checkTransaction(Q_FUNC_INFO); 0812 0813 // upon entry, we check that we could proceed with the operation 0814 if (!level) { 0815 if (!hasOnlyUnusedAccounts(account_list, 0)) { 0816 throw MYMONEYEXCEPTION_CSTRING("One or more accounts cannot be removed"); 0817 } 0818 } 0819 0820 // process all accounts in the list and test if they have transactions assigned 0821 foreach (const auto sAccount, account_list) { 0822 auto a = d->m_storage->account(sAccount); 0823 //qDebug() << "Deleting account '"<< a.name() << "'"; 0824 0825 // first remove all sub-accounts 0826 if (!a.accountList().isEmpty()) { 0827 removeAccountList(a.accountList(), level + 1); 0828 0829 // then remove account itself, but we first have to get 0830 // rid of the account list that is still stored in 0831 // the MyMoneyAccount object. Easiest way is to get a fresh copy. 0832 a = d->m_storage->account(sAccount); 0833 } 0834 0835 // make sure to remove the item from the cache 0836 removeAccount(a); 0837 } 0838 } 0839 0840 bool MyMoneyFile::hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level) 0841 { 0842 if (level > 100) 0843 throw MYMONEYEXCEPTION_CSTRING("Too deep recursion in [MyMoneyFile::hasOnlyUnusedAccounts]!"); 0844 // process all accounts in the list and test if they have transactions assigned 0845 for (const auto& sAccount : account_list) { 0846 if (transactionCount(sAccount) != 0) 0847 return false; // the current account has a transaction assigned 0848 if (!hasOnlyUnusedAccounts(account(sAccount).accountList(), level + 1)) 0849 return false; // some sub-account has a transaction assigned 0850 } 0851 return true; // all subaccounts unused 0852 } 0853 0854 0855 void MyMoneyFile::removeInstitution(const MyMoneyInstitution& institution) 0856 { 0857 d->checkTransaction(Q_FUNC_INFO); 0858 0859 MyMoneyInstitution inst = MyMoneyFile::institution(institution.id()); 0860 0861 bool blocked = signalsBlocked(); 0862 blockSignals(true); 0863 const auto accounts = inst.accountList(); 0864 for (const auto& acc : accounts) { 0865 auto a = account(acc); 0866 a.setInstitutionId(QString()); 0867 modifyAccount(a); 0868 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, a); 0869 } 0870 blockSignals(blocked); 0871 0872 d->m_storage->removeInstitution(institution); 0873 0874 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, institution); 0875 } 0876 0877 void MyMoneyFile::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) 0878 { 0879 // make sure we have a currency. If none is assigned, we assume base currency 0880 if (newAccount.currencyId().isEmpty()) 0881 newAccount.setCurrencyId(baseCurrency().id()); 0882 0883 MyMoneyFileTransaction ft; 0884 try { 0885 int pos; 0886 // check for ':' in the name and use it as separator for a hierarchy 0887 while ((pos = newAccount.name().indexOf(MyMoneyFile::AccountSeparator)) != -1) { 0888 QString part = newAccount.name().left(pos); 0889 QString remainder = newAccount.name().mid(pos + 1); 0890 const MyMoneyAccount& existingAccount = subAccountByName(parentAccount, part); 0891 if (existingAccount.id().isEmpty()) { 0892 newAccount.setName(part); 0893 0894 addAccount(newAccount, parentAccount); 0895 parentAccount = newAccount; 0896 } else { 0897 parentAccount = existingAccount; 0898 } 0899 newAccount.setParentAccountId(QString()); // make sure, there's no parent 0900 newAccount.clearId(); // and no id set for adding 0901 newAccount.removeAccountIds(); // and no sub-account ids 0902 newAccount.setName(remainder); 0903 } 0904 0905 addAccount(newAccount, parentAccount); 0906 0907 // in case of a loan account, we add the initial payment 0908 if ((newAccount.accountType() == Account::Type::Loan 0909 || newAccount.accountType() == Account::Type::AssetLoan) 0910 && !newAccount.value("kmm-loan-payment-acc").isEmpty() 0911 && !newAccount.value("kmm-loan-payment-date").isEmpty()) { 0912 MyMoneyAccountLoan acc(newAccount); 0913 MyMoneyTransaction t; 0914 MyMoneySplit a, b; 0915 a.setAccountId(acc.id()); 0916 b.setAccountId(acc.value("kmm-loan-payment-acc")); 0917 a.setValue(acc.loanAmount()); 0918 if (acc.accountType() == Account::Type::Loan) 0919 a.setValue(-a.value()); 0920 0921 a.setShares(a.value()); 0922 b.setValue(-a.value()); 0923 b.setShares(b.value()); 0924 a.setMemo(i18n("Loan payout")); 0925 b.setMemo(i18n("Loan payout")); 0926 t.setPostDate(QDate::fromString(acc.value("kmm-loan-payment-date"), Qt::ISODate)); 0927 newAccount.deletePair("kmm-loan-payment-acc"); 0928 newAccount.deletePair("kmm-loan-payment-date"); 0929 MyMoneyFile::instance()->modifyAccount(newAccount); 0930 0931 t.addSplit(a); 0932 t.addSplit(b); 0933 addTransaction(t); 0934 createOpeningBalanceTransaction(newAccount, openingBal); 0935 0936 // in case of an investment account we check if we should create 0937 // a brokerage account 0938 } else if (newAccount.accountType() == Account::Type::Investment 0939 && !brokerageAccount.name().isEmpty()) { 0940 addAccount(brokerageAccount, parentAccount); 0941 0942 // set a link from the investment account to the brokerage account 0943 modifyAccount(newAccount); 0944 createOpeningBalanceTransaction(brokerageAccount, openingBal); 0945 0946 } else 0947 createOpeningBalanceTransaction(newAccount, openingBal); 0948 0949 ft.commit(); 0950 } catch (const MyMoneyException &e) { 0951 qWarning("Unable to create account: %s", e.what()); 0952 throw; 0953 } 0954 } 0955 0956 void MyMoneyFile::addAccount(MyMoneyAccount& account, MyMoneyAccount& parent) 0957 { 0958 d->checkTransaction(Q_FUNC_INFO); 0959 0960 MyMoneyInstitution institution; 0961 0962 // perform some checks to see that the account stuff is OK. For 0963 // now we assume that the account must have a name, has no 0964 // transaction and sub-accounts and parent account 0965 // it's own ID is not set and it does not have a pointer to (MyMoneyFile) 0966 0967 if (account.name().length() == 0) 0968 throw MYMONEYEXCEPTION_CSTRING("Account has no name"); 0969 0970 if (account.id().length() != 0) 0971 throw MYMONEYEXCEPTION_CSTRING("New account must have no id"); 0972 0973 if (account.accountList().count() != 0) 0974 throw MYMONEYEXCEPTION_CSTRING("New account must have no sub-accounts"); 0975 0976 if (!account.parentAccountId().isEmpty()) 0977 throw MYMONEYEXCEPTION_CSTRING("New account must have no parent-id"); 0978 0979 if (account.accountType() == Account::Type::Unknown) 0980 throw MYMONEYEXCEPTION_CSTRING("Account has invalid type"); 0981 0982 // make sure, that the parent account exists 0983 // if not, an exception is thrown. If it exists, 0984 // get a copy of the current data 0985 auto acc = MyMoneyFile::account(parent.id()); 0986 0987 #if 0 0988 // TODO: remove the following code as we now can have multiple accounts 0989 // with the same name even in the same hierarchy position of the account tree 0990 // 0991 // check if the selected name is currently not among the child accounts 0992 // if we find one, then return it as the new account 0993 QStringList::const_iterator it_a; 0994 foreach (const auto accountID, acc.accountList()) { 0995 MyMoneyAccount a = MyMoneyFile::account(accountID); 0996 if (account.name() == a.name()) { 0997 account = a; 0998 return; 0999 } 1000 } 1001 #endif 1002 1003 // FIXME: make sure, that the parent has the same type 1004 // I left it out here because I don't know, if there is 1005 // a tight coupling between e.g. checking accounts and the 1006 // class asset. It certainly does not make sense to create an 1007 // expense account under an income account. Maybe it does, I don't know. 1008 1009 // We enforce, that a stock account can never be a parent and 1010 // that the parent for a stock account must be an investment. Also, 1011 // an investment cannot have another investment account as it's parent 1012 if (parent.isInvest()) 1013 throw MYMONEYEXCEPTION_CSTRING("Stock account cannot be parent account"); 1014 1015 if (account.isInvest() && parent.accountType() != Account::Type::Investment) 1016 throw MYMONEYEXCEPTION_CSTRING("Stock account must have investment account as parent "); 1017 1018 if (!account.isInvest() && parent.accountType() == Account::Type::Investment) 1019 throw MYMONEYEXCEPTION_CSTRING("Investment account can only have stock accounts as children"); 1020 1021 // if an institution is set, verify that it exists 1022 if (account.institutionId().length() != 0) { 1023 // check the presence of the institution. if it 1024 // does not exist, an exception is thrown 1025 institution = MyMoneyFile::institution(account.institutionId()); 1026 } 1027 1028 // if we don't have a valid opening date use today 1029 if (!account.openingDate().isValid()) { 1030 account.setOpeningDate(QDate::currentDate()); 1031 } 1032 1033 // make sure to set the opening date for categories to a 1034 // fixed date (1900-1-1). See #313793 on b.k.o for details 1035 if (account.isIncomeExpense()) { 1036 account.setOpeningDate(QDate(1900, 1, 1)); 1037 } 1038 1039 // if we don't have a currency assigned use the base currency 1040 if (account.currencyId().isEmpty()) { 1041 account.setCurrencyId(baseCurrency().id()); 1042 } 1043 1044 // make sure the parent id is setup 1045 account.setParentAccountId(parent.id()); 1046 1047 d->m_storage->addAccount(account); 1048 d->m_changeSet += MyMoneyNotification(File::Mode::Add, account); 1049 1050 d->m_storage->addAccount(parent, account); 1051 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); 1052 1053 if (account.institutionId().length() != 0) { 1054 institution.addAccountId(account.id()); 1055 d->m_storage->modifyInstitution(institution); 1056 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); 1057 } 1058 } 1059 1060 MyMoneyTransaction MyMoneyFile::createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance) 1061 { 1062 MyMoneyTransaction t; 1063 // if the opening balance is not zero, we need 1064 // to create the respective transaction 1065 if (!balance.isZero()) { 1066 d->checkTransaction(Q_FUNC_INFO); 1067 1068 MyMoneySecurity currency = security(acc.currencyId()); 1069 MyMoneyAccount openAcc = openingBalanceAccount(currency); 1070 1071 if (openAcc.openingDate() > acc.openingDate()) { 1072 openAcc.setOpeningDate(acc.openingDate()); 1073 modifyAccount(openAcc); 1074 } 1075 1076 MyMoneySplit s; 1077 1078 t.setPostDate(acc.openingDate()); 1079 t.setCommodity(acc.currencyId()); 1080 1081 s.setAccountId(acc.id()); 1082 s.setShares(balance); 1083 s.setValue(balance); 1084 t.addSplit(s); 1085 1086 s.clearId(); 1087 s.setAccountId(openAcc.id()); 1088 s.setShares(-balance); 1089 s.setValue(-balance); 1090 t.addSplit(s); 1091 1092 addTransaction(t); 1093 } 1094 return t; 1095 } 1096 1097 QString MyMoneyFile::openingBalanceTransaction(const MyMoneyAccount& acc) const 1098 { 1099 QString result; 1100 1101 MyMoneySecurity currency = security(acc.currencyId()); 1102 MyMoneyAccount openAcc; 1103 1104 try { 1105 openAcc = openingBalanceAccount(currency); 1106 } catch (const MyMoneyException &) { 1107 return result; 1108 } 1109 1110 // Iterate over all the opening balance transactions for this currency 1111 MyMoneyTransactionFilter filter; 1112 filter.addAccount(openAcc.id()); 1113 QList<MyMoneyTransaction> transactions = transactionList(filter); 1114 QList<MyMoneyTransaction>::const_iterator it_t = transactions.constBegin(); 1115 while (it_t != transactions.constEnd()) { 1116 try { 1117 // Test whether the transaction also includes a split into 1118 // this account 1119 (*it_t).splitByAccount(acc.id(), true /*match*/); 1120 1121 // If so, we have a winner! 1122 result = (*it_t).id(); 1123 break; 1124 } catch (const MyMoneyException &) { 1125 // If not, keep searching 1126 ++it_t; 1127 } 1128 } 1129 1130 return result; 1131 } 1132 1133 MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) 1134 { 1135 if (!security.isCurrency()) 1136 throw MYMONEYEXCEPTION_CSTRING("Opening balance for non currencies not supported"); 1137 1138 try { 1139 return openingBalanceAccount_internal(security); 1140 } catch (const MyMoneyException &) { 1141 MyMoneyFileTransaction ft; 1142 MyMoneyAccount acc; 1143 1144 try { 1145 acc = createOpeningBalanceAccount(security); 1146 ft.commit(); 1147 1148 } catch (const MyMoneyException &) { 1149 qDebug("Unable to create opening balance account for security %s", qPrintable(security.id())); 1150 } 1151 return acc; 1152 } 1153 } 1154 1155 MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const 1156 { 1157 return openingBalanceAccount_internal(security); 1158 } 1159 1160 MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const 1161 { 1162 if (!security.isCurrency()) 1163 throw MYMONEYEXCEPTION_CSTRING("Opening balance for non currencies not supported"); 1164 1165 MyMoneyAccount acc; 1166 QList<MyMoneyAccount> accounts; 1167 QList<MyMoneyAccount>::ConstIterator it; 1168 1169 accountList(accounts, equity().accountList(), true); 1170 1171 // If an account is clearly marked as an opening balance account 1172 // for a specific currency we use it 1173 for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { 1174 if ((it->value("OpeningBalanceAccount") == QLatin1String("Yes")) && (it->currencyId() == security.id())) { 1175 acc = *it; 1176 break; 1177 } 1178 } 1179 1180 // If we did not find one, then we look for one with a 1181 // name starting with the opening balances prefix. 1182 if (acc.id().isEmpty()) { 1183 for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { 1184 if (it->name().startsWith(MyMoneyFile::openingBalancesPrefix()) && (it->currencyId() == security.id())) { 1185 acc = *it; 1186 break; 1187 } 1188 } 1189 } 1190 1191 // If we still don't have an opening balances account, we 1192 // see if we can find one of type equity with the currency 1193 // in question. This is needed, in case we have an old file 1194 // which does not have the marker of step 1 above because 1195 // is not (yet) present, the name did not match because it 1196 // has been replaced (e.g. anonymization or the language 1197 // of the application has been changed) 1198 if (acc.id().isEmpty()) { 1199 for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { 1200 if ((it->accountType() == eMyMoney::Account::Type::Equity) && (it->currencyId() == security.id())) { 1201 acc = *it; 1202 break; 1203 } 1204 } 1205 } 1206 1207 if (acc.id().isEmpty()) 1208 throw MYMONEYEXCEPTION(QString::fromLatin1("No opening balance account for %1").arg(security.tradingSymbol())); 1209 1210 return acc; 1211 } 1212 1213 MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security) 1214 { 1215 d->checkTransaction(Q_FUNC_INFO); 1216 1217 MyMoneyAccount acc; 1218 QList<MyMoneyAccount> accounts; 1219 QList<MyMoneyAccount>::ConstIterator it; 1220 1221 accountList(accounts, equity().accountList(), true); 1222 1223 // find present opening balance accounts without containing '(' 1224 QString name; 1225 QString parentAccountId; 1226 QRegExp exp(QString("\\([A-Z]{3}\\)")); 1227 1228 for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { 1229 if (it->value("OpeningBalanceAccount") == QLatin1String("Yes") 1230 && exp.indexIn(it->name()) == -1) { 1231 name = it->name(); 1232 parentAccountId = it->parentAccountId(); 1233 break; 1234 } 1235 } 1236 1237 if (name.isEmpty()) 1238 name = MyMoneyFile::openingBalancesPrefix(); 1239 if (security.id() != baseCurrency().id()) { 1240 name += QString(" (%1)").arg(security.id()); 1241 } 1242 acc.setName(name); 1243 acc.setAccountType(Account::Type::Equity); 1244 acc.setCurrencyId(security.id()); 1245 acc.setValue("OpeningBalanceAccount", "Yes"); 1246 1247 MyMoneyAccount parent = !parentAccountId.isEmpty() ? account(parentAccountId) : equity(); 1248 this->addAccount(acc, parent); 1249 return acc; 1250 } 1251 1252 void MyMoneyFile::addTransaction(MyMoneyTransaction& transaction) 1253 { 1254 d->checkTransaction(Q_FUNC_INFO); 1255 1256 // clear all changed objects from cache 1257 MyMoneyNotifier notifier(d); 1258 1259 // perform some checks to see that the transaction stuff is OK. For 1260 // now we assume that 1261 // * no ids are assigned 1262 // * the date valid (must not be empty) 1263 // * the referenced accounts in the splits exist 1264 1265 // first perform all the checks 1266 if (!transaction.id().isEmpty()) 1267 throw MYMONEYEXCEPTION_CSTRING("Unable to add transaction with id set"); 1268 if (!transaction.postDate().isValid()) 1269 throw MYMONEYEXCEPTION_CSTRING("Unable to add transaction with invalid postdate"); 1270 1271 // now check the splits 1272 auto loanAccountAffected = false; 1273 const auto splits1 = transaction.splits(); 1274 for (const auto& split : splits1) { 1275 // the following line will throw an exception if the 1276 // account does not exist or is one of the standard accounts 1277 auto acc = MyMoneyFile::account(split.accountId()); 1278 if (acc.id().isEmpty()) 1279 throw MYMONEYEXCEPTION_CSTRING("Cannot add split with no account assigned"); 1280 if (acc.isLoan()) 1281 loanAccountAffected = true; 1282 if (isStandardAccount(split.accountId())) 1283 throw MYMONEYEXCEPTION_CSTRING("Cannot add split referencing standard account"); 1284 } 1285 1286 // change transfer splits between asset/liability and loan accounts 1287 // into amortization splits 1288 if (loanAccountAffected) { 1289 foreach (const auto split, transaction.splits()) { 1290 if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) { 1291 auto acc = MyMoneyFile::account(split.accountId()); 1292 1293 if (acc.isAssetLiability()) { 1294 MyMoneySplit s = split; 1295 s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); 1296 transaction.modifySplit(s); 1297 } 1298 } 1299 } 1300 } 1301 1302 // check that we have a commodity 1303 if (transaction.commodity().isEmpty()) { 1304 transaction.setCommodity(baseCurrency().id()); 1305 } 1306 1307 // make sure the value is rounded to the accounts precision 1308 fixSplitPrecision(transaction); 1309 1310 // then add the transaction to the file global pool 1311 d->m_storage->addTransaction(transaction); 1312 1313 // scan the splits again to update notification list 1314 const auto splits2 = transaction.splits(); 1315 for (const auto& split : splits2) 1316 d->addCacheNotification(split.accountId(), transaction.postDate()); 1317 1318 d->m_changeSet += MyMoneyNotification(File::Mode::Add, transaction); 1319 } 1320 1321 MyMoneyTransaction MyMoneyFile::transaction(const QString& id) const 1322 { 1323 d->checkStorage(); 1324 1325 return d->m_storage->transaction(id); 1326 } 1327 1328 MyMoneyTransaction MyMoneyFile::transaction(const QString& account, const int idx) const 1329 { 1330 d->checkStorage(); 1331 1332 return d->m_storage->transaction(account, idx); 1333 } 1334 1335 void MyMoneyFile::addPayee(MyMoneyPayee& payee) 1336 { 1337 d->checkTransaction(Q_FUNC_INFO); 1338 1339 d->m_storage->addPayee(payee); 1340 d->m_changeSet += MyMoneyNotification(File::Mode::Add, payee); 1341 } 1342 1343 MyMoneyPayee MyMoneyFile::payee(const QString& id) const 1344 { 1345 if (Q_UNLIKELY(id.isEmpty())) 1346 return MyMoneyPayee(); 1347 1348 return d->m_storage->payee(id); 1349 } 1350 1351 MyMoneyPayee MyMoneyFile::payeeByName(const QString& name) const 1352 { 1353 d->checkStorage(); 1354 1355 return d->m_storage->payeeByName(name); 1356 } 1357 1358 void MyMoneyFile::modifyPayee(const MyMoneyPayee& payee) 1359 { 1360 d->checkTransaction(Q_FUNC_INFO); 1361 1362 d->m_storage->modifyPayee(payee); 1363 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, payee); 1364 } 1365 1366 void MyMoneyFile::removePayee(const MyMoneyPayee& payee) 1367 { 1368 d->checkTransaction(Q_FUNC_INFO); 1369 1370 // FIXME we need to make sure, that the payee is not referenced anymore 1371 d->m_storage->removePayee(payee); 1372 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, payee); 1373 } 1374 1375 void MyMoneyFile::addTag(MyMoneyTag& tag) 1376 { 1377 d->checkTransaction(Q_FUNC_INFO); 1378 1379 d->m_storage->addTag(tag); 1380 d->m_changeSet += MyMoneyNotification(File::Mode::Add, tag); 1381 } 1382 1383 MyMoneyTag MyMoneyFile::tag(const QString& id) const 1384 { 1385 return d->m_storage->tag(id); 1386 } 1387 1388 MyMoneyTag MyMoneyFile::tagByName(const QString& name) const 1389 { 1390 d->checkStorage(); 1391 1392 return d->m_storage->tagByName(name); 1393 } 1394 1395 void MyMoneyFile::modifyTag(const MyMoneyTag& tag) 1396 { 1397 d->checkTransaction(Q_FUNC_INFO); 1398 1399 d->m_storage->modifyTag(tag); 1400 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, tag); 1401 } 1402 1403 void MyMoneyFile::removeTag(const MyMoneyTag& tag) 1404 { 1405 d->checkTransaction(Q_FUNC_INFO); 1406 1407 // FIXME we need to make sure, that the tag is not referenced anymore 1408 d->m_storage->removeTag(tag); 1409 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, tag); 1410 } 1411 1412 void MyMoneyFile::accountList(QList<MyMoneyAccount>& list, const QStringList& idlist, const bool recursive) const 1413 { 1414 d->checkStorage(); 1415 1416 if (idlist.isEmpty()) { 1417 d->m_storage->accountList(list); 1418 1419 #if 0 1420 // TODO: I have no idea what this was good for, but it caused the networth report 1421 // to show double the numbers so I commented it out (ipwizard, 2008-05-24) 1422 if (d->m_storage && (list.isEmpty() || list.size() != d->m_storage->accountCount())) { 1423 d->m_storage->accountList(list); 1424 d->m_cache.preloadAccount(list); 1425 } 1426 #endif 1427 1428 QList<MyMoneyAccount>::Iterator it; 1429 for (it = list.begin(); it != list.end();) { 1430 if (isStandardAccount((*it).id())) { 1431 it = list.erase(it); 1432 } else { 1433 ++it; 1434 } 1435 } 1436 } else { 1437 QList<MyMoneyAccount>::ConstIterator it; 1438 QList<MyMoneyAccount> list_a; 1439 d->m_storage->accountList(list_a); 1440 1441 for (it = list_a.constBegin(); it != list_a.constEnd(); ++it) { 1442 if (!isStandardAccount((*it).id())) { 1443 if (idlist.indexOf((*it).id()) != -1) { 1444 list.append(*it); 1445 if (recursive == true && !(*it).accountList().isEmpty()) { 1446 accountList(list, (*it).accountList(), true); 1447 } 1448 } 1449 } 1450 } 1451 } 1452 } 1453 1454 QList<MyMoneyInstitution> MyMoneyFile::institutionList() const 1455 { 1456 return d->m_storage->institutionList(); 1457 } 1458 1459 // general get functions 1460 MyMoneyPayee MyMoneyFile::user() const 1461 { 1462 d->checkStorage(); 1463 return d->m_storage->user(); 1464 } 1465 1466 // general set functions 1467 void MyMoneyFile::setUser(const MyMoneyPayee& user) 1468 { 1469 d->checkTransaction(Q_FUNC_INFO); 1470 1471 d->m_storage->setUser(user); 1472 } 1473 1474 bool MyMoneyFile::dirty() const 1475 { 1476 if (!d->m_storage) 1477 return false; 1478 1479 return d->m_storage->dirty(); 1480 } 1481 1482 void MyMoneyFile::setDirty() const 1483 { 1484 d->checkStorage(); 1485 1486 d->m_storage->setDirty(); 1487 } 1488 1489 unsigned int MyMoneyFile::accountCount() const 1490 { 1491 d->checkStorage(); 1492 1493 return d->m_storage->accountCount(); 1494 } 1495 1496 void MyMoneyFile::ensureDefaultCurrency(MyMoneyAccount& acc) const 1497 { 1498 if (acc.currencyId().isEmpty()) { 1499 if (!baseCurrency().id().isEmpty()) 1500 acc.setCurrencyId(baseCurrency().id()); 1501 } 1502 } 1503 1504 MyMoneyAccount MyMoneyFile::liability() const 1505 { 1506 d->checkStorage(); 1507 1508 return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Liability)); 1509 } 1510 1511 MyMoneyAccount MyMoneyFile::asset() const 1512 { 1513 d->checkStorage(); 1514 1515 return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Asset)); 1516 } 1517 1518 MyMoneyAccount MyMoneyFile::expense() const 1519 { 1520 d->checkStorage(); 1521 1522 return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Expense)); 1523 } 1524 1525 MyMoneyAccount MyMoneyFile::income() const 1526 { 1527 d->checkStorage(); 1528 1529 return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Income)); 1530 } 1531 1532 MyMoneyAccount MyMoneyFile::equity() const 1533 { 1534 d->checkStorage(); 1535 1536 return account(MyMoneyAccount::stdAccName(eMyMoney::Account::Standard::Equity)); 1537 } 1538 1539 unsigned int MyMoneyFile::transactionCount(const QString& account) const 1540 { 1541 d->checkStorage(); 1542 1543 return d->m_storage->transactionCount(account); 1544 } 1545 1546 unsigned int MyMoneyFile::transactionCount() const 1547 { 1548 return transactionCount(QString()); 1549 } 1550 1551 QMap<QString, unsigned long> MyMoneyFile::transactionCountMap() const 1552 { 1553 d->checkStorage(); 1554 1555 return d->m_storage->transactionCountMap(); 1556 } 1557 1558 unsigned int MyMoneyFile::institutionCount() const 1559 { 1560 d->checkStorage(); 1561 1562 return d->m_storage->institutionCount(); 1563 } 1564 1565 MyMoneyMoney MyMoneyFile::balance(const QString& id, const QDate& date) const 1566 { 1567 if (date.isValid()) { 1568 MyMoneyBalanceCacheItem bal = d->m_balanceCache.balance(id, date); 1569 if (bal.isValid()) 1570 return bal.balance(); 1571 } 1572 1573 d->checkStorage(); 1574 1575 MyMoneyMoney returnValue = d->m_storage->balance(id, date); 1576 1577 if (date.isValid()) { 1578 d->m_balanceCache.insert(id, date, returnValue); 1579 } 1580 1581 return returnValue; 1582 } 1583 1584 MyMoneyMoney MyMoneyFile::balance(const QString& id) const 1585 { 1586 return balance(id, QDate()); 1587 } 1588 1589 MyMoneyMoney MyMoneyFile::clearedBalance(const QString &id, const QDate& date) const 1590 { 1591 MyMoneyMoney cleared; 1592 QList<MyMoneyTransaction> list; 1593 1594 cleared = balance(id, date); 1595 1596 MyMoneyAccount account = this->account(id); 1597 MyMoneyMoney factor(1, 1); 1598 if (account.accountGroup() == Account::Type::Liability || account.accountGroup() == Account::Type::Equity) 1599 factor = -factor; 1600 1601 MyMoneyTransactionFilter filter; 1602 filter.addAccount(id); 1603 filter.setDateFilter(QDate(), date); 1604 filter.setReportAllSplits(false); 1605 filter.addState((int)TransactionFilter::State::NotReconciled); 1606 transactionList(list, filter); 1607 1608 for (QList<MyMoneyTransaction>::const_iterator it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { 1609 const QList<MyMoneySplit>& splits = (*it_t).splits(); 1610 for (QList<MyMoneySplit>::const_iterator it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { 1611 const MyMoneySplit &split = (*it_s); 1612 if (split.accountId() != id) 1613 continue; 1614 cleared -= split.shares(); 1615 } 1616 } 1617 return cleared * factor; 1618 } 1619 1620 MyMoneyMoney MyMoneyFile::totalBalance(const QString& id, const QDate& date) const 1621 { 1622 d->checkStorage(); 1623 1624 return d->m_storage->totalBalance(id, date); 1625 } 1626 1627 MyMoneyMoney MyMoneyFile::totalBalance(const QString& id) const 1628 { 1629 return totalBalance(id, QDate()); 1630 } 1631 1632 void MyMoneyFile::warningMissingRate(const QString& fromId, const QString& toId) const 1633 { 1634 MyMoneySecurity from, to; 1635 try { 1636 from = security(fromId); 1637 to = security(toId); 1638 qWarning("Missing price info for conversion from %s to %s", qPrintable(from.name()), qPrintable(to.name())); 1639 1640 } catch (const MyMoneyException &e) { 1641 qWarning("Missing security caught in MyMoneyFile::warningMissingRate(). %s", e.what()); 1642 } 1643 } 1644 1645 void MyMoneyFile::transactionList(QList<QPair<MyMoneyTransaction, MyMoneySplit> >& list, MyMoneyTransactionFilter& filter) const 1646 { 1647 d->checkStorage(); 1648 d->m_storage->transactionList(list, filter); 1649 } 1650 1651 void MyMoneyFile::transactionList(QList<MyMoneyTransaction>& list, MyMoneyTransactionFilter& filter) const 1652 { 1653 d->checkStorage(); 1654 d->m_storage->transactionList(list, filter); 1655 } 1656 1657 QList<MyMoneyTransaction> MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const 1658 { 1659 d->checkStorage(); 1660 return d->m_storage->transactionList(filter); 1661 } 1662 1663 QList<MyMoneyPayee> MyMoneyFile::payeeList() const 1664 { 1665 return d->m_storage->payeeList(); 1666 } 1667 1668 QList<MyMoneyTag> MyMoneyFile::tagList() const 1669 { 1670 return d->m_storage->tagList(); 1671 } 1672 1673 QString MyMoneyFile::accountToCategory(const QString& accountId, bool includeStandardAccounts) const 1674 { 1675 MyMoneyAccount acc; 1676 QString rc; 1677 1678 if (!accountId.isEmpty()) { 1679 acc = account(accountId); 1680 do { 1681 if (!rc.isEmpty()) 1682 rc = AccountSeparator + rc; 1683 rc = acc.name() + rc; 1684 acc = account(acc.parentAccountId()); 1685 } while (!acc.id().isEmpty() && (includeStandardAccounts || !isStandardAccount(acc.id()))); 1686 } 1687 return rc; 1688 } 1689 1690 QString MyMoneyFile::categoryToAccount(const QString& category, Account::Type type) const 1691 { 1692 QString id; 1693 1694 // search the category in the expense accounts and if it is not found, try 1695 // to locate it in the income accounts 1696 if (type == Account::Type::Unknown 1697 || type == Account::Type::Expense) { 1698 id = locateSubAccount(MyMoneyFile::instance()->expense(), category); 1699 } 1700 1701 if ((id.isEmpty() && type == Account::Type::Unknown) 1702 || type == Account::Type::Income) { 1703 id = locateSubAccount(MyMoneyFile::instance()->income(), category); 1704 } 1705 1706 return id; 1707 } 1708 1709 QString MyMoneyFile::categoryToAccount(const QString& category) const 1710 { 1711 return categoryToAccount(category, Account::Type::Unknown); 1712 } 1713 1714 QString MyMoneyFile::nameToAccount(const QString& name) const 1715 { 1716 QString id; 1717 1718 // search the category in the asset accounts and if it is not found, try 1719 // to locate it in the liability accounts 1720 id = locateSubAccount(MyMoneyFile::instance()->asset(), name); 1721 if (id.isEmpty()) 1722 id = locateSubAccount(MyMoneyFile::instance()->liability(), name); 1723 1724 return id; 1725 } 1726 1727 QString MyMoneyFile::parentName(const QString& name) const 1728 { 1729 return name.section(AccountSeparator, 0, -2); 1730 } 1731 1732 QString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const QString& category) const 1733 { 1734 MyMoneyAccount nextBase; 1735 QString level, remainder; 1736 level = category.section(AccountSeparator, 0, 0); 1737 remainder = category.section(AccountSeparator, 1); 1738 1739 foreach (const auto sAccount, base.accountList()) { 1740 nextBase = account(sAccount); 1741 if (nextBase.name() == level) { 1742 if (remainder.isEmpty()) { 1743 return nextBase.id(); 1744 } 1745 return locateSubAccount(nextBase, remainder); 1746 } 1747 } 1748 return QString(); 1749 } 1750 1751 QString MyMoneyFile::value(const QString& key) const 1752 { 1753 d->checkStorage(); 1754 1755 return d->m_storage->value(key); 1756 } 1757 1758 void MyMoneyFile::setValue(const QString& key, const QString& val) 1759 { 1760 d->checkTransaction(Q_FUNC_INFO); 1761 1762 d->m_storage->setValue(key, val); 1763 } 1764 1765 void MyMoneyFile::deletePair(const QString& key) 1766 { 1767 d->checkTransaction(Q_FUNC_INFO); 1768 1769 d->m_storage->deletePair(key); 1770 } 1771 1772 void MyMoneyFile::addSchedule(MyMoneySchedule& sched) 1773 { 1774 d->checkTransaction(Q_FUNC_INFO); 1775 1776 const auto splits = sched.transaction().splits(); 1777 for (const auto& split : splits) { 1778 // the following line will throw an exception if the 1779 // account does not exist or is one of the standard accounts 1780 const auto acc = account(split.accountId()); 1781 if (acc.id().isEmpty()) 1782 throw MYMONEYEXCEPTION_CSTRING("Cannot add split with no account assigned"); 1783 if (isStandardAccount(split.accountId())) 1784 throw MYMONEYEXCEPTION_CSTRING("Cannot add split referencing standard account"); 1785 } 1786 1787 d->m_storage->addSchedule(sched); 1788 d->m_changeSet += MyMoneyNotification(File::Mode::Add, sched); 1789 } 1790 1791 void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched) 1792 { 1793 d->checkTransaction(Q_FUNC_INFO); 1794 1795 foreach (const auto split, sched.transaction().splits()) { 1796 // the following line will throw an exception if the 1797 // account does not exist or is one of the standard accounts 1798 auto acc = MyMoneyFile::account(split.accountId()); 1799 if (acc.id().isEmpty()) 1800 throw MYMONEYEXCEPTION_CSTRING("Cannot store split with no account assigned"); 1801 if (isStandardAccount(split.accountId())) 1802 throw MYMONEYEXCEPTION_CSTRING("Cannot store split referencing standard account"); 1803 } 1804 1805 d->m_storage->modifySchedule(sched); 1806 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, sched); 1807 } 1808 1809 void MyMoneyFile::removeSchedule(const MyMoneySchedule& sched) 1810 { 1811 d->checkTransaction(Q_FUNC_INFO); 1812 1813 d->m_storage->removeSchedule(sched); 1814 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, sched); 1815 } 1816 1817 MyMoneySchedule MyMoneyFile::schedule(const QString& id) const 1818 { 1819 return d->m_storage->schedule(id); 1820 } 1821 1822 QList<MyMoneySchedule> MyMoneyFile::scheduleList( 1823 const QString& accountId, 1824 const Schedule::Type type, 1825 const Schedule::Occurrence occurrence, 1826 const Schedule::PaymentType paymentType, 1827 const QDate& startDate, 1828 const QDate& endDate, 1829 const bool overdue) const 1830 { 1831 d->checkStorage(); 1832 1833 return d->m_storage->scheduleList(accountId, type, occurrence, paymentType, startDate, endDate, overdue); 1834 } 1835 1836 QList<MyMoneySchedule> MyMoneyFile::scheduleList( 1837 const QString& accountId) const 1838 { 1839 return scheduleList(accountId, Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, 1840 QDate(), QDate(), false); 1841 } 1842 1843 QList<MyMoneySchedule> MyMoneyFile::scheduleList() const 1844 { 1845 return scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, 1846 QDate(), QDate(), false); 1847 } 1848 1849 QStringList MyMoneyFile::consistencyCheck() 1850 { 1851 QList<MyMoneyAccount> list; 1852 QList<MyMoneyAccount>::Iterator it_a; 1853 QList<MyMoneySchedule>::Iterator it_sch; 1854 QList<MyMoneyPayee>::Iterator it_p; 1855 QList<MyMoneyTransaction>::Iterator it_t; 1856 QList<MyMoneyReport>::Iterator it_r; 1857 QStringList accountRebuild; 1858 1859 QMap<QString, bool> interestAccounts; 1860 1861 MyMoneyAccount parent; 1862 MyMoneyAccount child; 1863 MyMoneyAccount toplevel; 1864 1865 QString parentId; 1866 QStringList rc; 1867 1868 int problemCount = 0; 1869 int unfixedCount = 0; 1870 QString problemAccount; 1871 1872 // check that we have a storage object 1873 d->checkTransaction(Q_FUNC_INFO); 1874 1875 // get the current list of accounts 1876 accountList(list); 1877 // add the standard accounts 1878 list << MyMoneyFile::instance()->asset(); 1879 list << MyMoneyFile::instance()->liability(); 1880 list << MyMoneyFile::instance()->income(); 1881 list << MyMoneyFile::instance()->expense(); 1882 1883 for (it_a = list.begin(); it_a != list.end(); ++it_a) { 1884 // no more checks for standard accounts 1885 if (isStandardAccount((*it_a).id())) { 1886 continue; 1887 } 1888 1889 switch ((*it_a).accountGroup()) { 1890 case Account::Type::Asset: 1891 toplevel = asset(); 1892 break; 1893 case Account::Type::Liability: 1894 toplevel = liability(); 1895 break; 1896 case Account::Type::Expense: 1897 toplevel = expense(); 1898 break; 1899 case Account::Type::Income: 1900 toplevel = income(); 1901 break; 1902 case Account::Type::Equity: 1903 toplevel = equity(); 1904 break; 1905 default: 1906 qWarning("%s:%d This should never happen!", __FILE__, __LINE__); 1907 break; 1908 } 1909 1910 // check for loops in the hierarchy 1911 parentId = (*it_a).parentAccountId(); 1912 try { 1913 bool dropOut = false; 1914 while (!isStandardAccount(parentId) && !dropOut) { 1915 parent = account(parentId); 1916 if (parent.id() == (*it_a).id()) { 1917 // parent loops, so we need to re-parent to toplevel account 1918 // find parent account in our list 1919 problemCount++; 1920 QList<MyMoneyAccount>::Iterator it_b; 1921 for (it_b = list.begin(); it_b != list.end(); ++it_b) { 1922 if ((*it_b).id() == parent.id()) { 1923 if (problemAccount != (*it_a).name()) { 1924 problemAccount = (*it_a).name(); 1925 rc << i18n("* Problem with account '%1'", problemAccount); 1926 rc << i18n(" * Loop detected between this account and account '%1'.", (*it_b).name()); 1927 rc << i18n(" Reparenting account '%2' to top level account '%1'.", toplevel.name(), (*it_a).name()); 1928 (*it_a).setParentAccountId(toplevel.id()); 1929 if (accountRebuild.contains(toplevel.id()) == 0) 1930 accountRebuild << toplevel.id(); 1931 if (accountRebuild.contains((*it_a).id()) == 0) 1932 accountRebuild << (*it_a).id(); 1933 dropOut = true; 1934 break; 1935 } 1936 } 1937 } 1938 } 1939 parentId = parent.parentAccountId(); 1940 } 1941 1942 } catch (const MyMoneyException &) { 1943 // if we don't know about a parent, we catch it later 1944 } 1945 1946 // check that the parent exists 1947 parentId = (*it_a).parentAccountId(); 1948 try { 1949 parent = account(parentId); 1950 if ((*it_a).accountGroup() != parent.accountGroup()) { 1951 problemCount++; 1952 if (problemAccount != (*it_a).name()) { 1953 problemAccount = (*it_a).name(); 1954 rc << i18n("* Problem with account '%1'", problemAccount); 1955 } 1956 // the parent belongs to a different group, so we reconnect to the 1957 // master group account (asset, liability, etc) to which this account 1958 // should belong and update it in the engine. 1959 rc << i18n(" * Parent account '%1' belongs to a different group.", parent.name()); 1960 rc << i18n(" New parent account is the top level account '%1'.", toplevel.name()); 1961 (*it_a).setParentAccountId(toplevel.id()); 1962 1963 // make sure to rebuild the sub-accounts of the top account 1964 // and the one we removed this account from 1965 if (accountRebuild.contains(toplevel.id()) == 0) 1966 accountRebuild << toplevel.id(); 1967 if (accountRebuild.contains(parent.id()) == 0) 1968 accountRebuild << parent.id(); 1969 } else if (!parent.accountList().contains((*it_a).id())) { 1970 problemCount++; 1971 if (problemAccount != (*it_a).name()) { 1972 problemAccount = (*it_a).name(); 1973 rc << i18n("* Problem with account '%1'", problemAccount); 1974 } 1975 // parent exists, but does not have a reference to the account 1976 rc << i18n(" * Parent account '%1' does not contain '%2' as sub-account.", parent.name(), problemAccount); 1977 if (accountRebuild.contains(parent.id()) == 0) 1978 accountRebuild << parent.id(); 1979 } 1980 } catch (const MyMoneyException &) { 1981 // apparently, the parent does not exist anymore. we reconnect to the 1982 // master group account (asset, liability, etc) to which this account 1983 // should belong and update it in the engine. 1984 problemCount++; 1985 if (problemAccount != (*it_a).name()) { 1986 problemAccount = (*it_a).name(); 1987 rc << i18n("* Problem with account '%1'", problemAccount); 1988 } 1989 rc << i18n(" * The parent with id %1 does not exist anymore.", parentId); 1990 rc << i18n(" New parent account is the top level account '%1'.", toplevel.name()); 1991 (*it_a).setParentAccountId(toplevel.id()); 1992 1993 // make sure to rebuild the sub-accounts of the top account 1994 if (accountRebuild.contains(toplevel.id()) == 0) 1995 accountRebuild << toplevel.id(); 1996 } 1997 1998 // now check that all the children exist and have the correct type 1999 foreach (const auto accountID, (*it_a).accountList()) { 2000 // check that the child exists 2001 try { 2002 child = account(accountID); 2003 if (child.parentAccountId() != (*it_a).id()) { 2004 throw MYMONEYEXCEPTION_CSTRING("Child account has a different parent"); 2005 } 2006 } catch (const MyMoneyException &) { 2007 problemCount++; 2008 if (problemAccount != (*it_a).name()) { 2009 problemAccount = (*it_a).name(); 2010 rc << i18n("* Problem with account '%1'", problemAccount); 2011 } 2012 rc << i18n(" * Child account with id %1 does not exist anymore.", accountID); 2013 rc << i18n(" The child account list will be reconstructed."); 2014 if (accountRebuild.contains((*it_a).id()) == 0) 2015 accountRebuild << (*it_a).id(); 2016 } 2017 } 2018 2019 // see if it is a loan account. if so, remember the assigned interest account 2020 if ((*it_a).isLoan()) { 2021 MyMoneyAccountLoan loan(*it_a); 2022 if (!loan.interestAccountId().isEmpty()) { 2023 interestAccounts[loan.interestAccountId()] = true; 2024 } 2025 try { 2026 payee(loan.payee()); 2027 } catch (const MyMoneyException &) { 2028 problemCount++; 2029 if (problemAccount != (*it_a).name()) { 2030 problemAccount = (*it_a).name(); 2031 rc << i18n("* Problem with account '%1'", problemAccount); 2032 } 2033 rc << i18n(" * The payee with id %1 referenced by the loan does not exist anymore.", loan.payee()); 2034 rc << i18n(" The payee will be removed."); 2035 // remove the payee - the account will be modified in the engine later 2036 (*it_a).deletePair("payee"); 2037 } 2038 } 2039 2040 // check if it is a category and set the date to 1900-01-01 if different 2041 if ((*it_a).isIncomeExpense()) { 2042 if (((*it_a).openingDate().isValid() == false) || ((*it_a).openingDate() != QDate(1900, 1, 1))) { 2043 (*it_a).setOpeningDate(QDate(1900, 1, 1)); 2044 } 2045 } 2046 2047 // check for clear text online password in the online settings 2048 if (!(*it_a).onlineBankingSettings().value("password").isEmpty()) { 2049 if (problemAccount != (*it_a).name()) { 2050 problemAccount = (*it_a).name(); 2051 rc << i18n("* Problem with account '%1'", problemAccount); 2052 } 2053 rc << i18n(" * Older versions of KMyMoney stored an OFX password for this account in cleartext."); 2054 rc << i18n(" Please open it in the account editor (Account/Edit account) once and press OK."); 2055 rc << i18n(" This will store the password in the KDE wallet and remove the cleartext version."); 2056 ++unfixedCount; 2057 } 2058 2059 // if the account was modified, we need to update it in the engine 2060 if (!(d->m_storage->account((*it_a).id()) == (*it_a))) { 2061 try { 2062 d->m_storage->modifyAccount(*it_a, true); 2063 } catch (const MyMoneyException &) { 2064 rc << i18n(" * Unable to update account data in engine."); 2065 return rc; 2066 } 2067 } 2068 } 2069 2070 if (accountRebuild.count() != 0) { 2071 rc << i18n("* Reconstructing the child lists for"); 2072 } 2073 2074 // clear the affected lists 2075 for (it_a = list.begin(); it_a != list.end(); ++it_a) { 2076 if (accountRebuild.contains((*it_a).id())) { 2077 rc << QString(" %1").arg((*it_a).name()); 2078 // clear the account list 2079 (*it_a).removeAccountIds(); 2080 } 2081 } 2082 2083 // reconstruct the lists 2084 for (it_a = list.begin(); it_a != list.end(); ++it_a) { 2085 QList<MyMoneyAccount>::Iterator it; 2086 parentId = (*it_a).parentAccountId(); 2087 if (accountRebuild.contains(parentId)) { 2088 for (it = list.begin(); it != list.end(); ++it) { 2089 if ((*it).id() == parentId) { 2090 (*it).addAccountId((*it_a).id()); 2091 break; 2092 } 2093 } 2094 } 2095 } 2096 2097 // update the engine objects 2098 for (it_a = list.begin(); it_a != list.end(); ++it_a) { 2099 if (accountRebuild.contains((*it_a).id())) { 2100 try { 2101 d->m_storage->modifyAccount(*it_a, true); 2102 } catch (const MyMoneyException &) { 2103 rc << i18n(" * Unable to update account data for account %1 in engine", (*it_a).name()); 2104 } 2105 } 2106 } 2107 2108 // For some reason, files exist with invalid ids. This has been found in the payee id 2109 // so we fix them here 2110 QList<MyMoneyPayee> pList = payeeList(); 2111 QMap<QString, QString>payeeConversionMap; 2112 2113 for (it_p = pList.begin(); it_p != pList.end(); ++it_p) { 2114 if ((*it_p).id().length() > 7) { 2115 // found one of those with an invalid ids 2116 // create a new one and store it in the map. 2117 MyMoneyPayee payee = (*it_p); 2118 payee.clearId(); 2119 d->m_storage->addPayee(payee); 2120 payeeConversionMap[(*it_p).id()] = payee.id(); 2121 rc << i18n(" * Payee %1 recreated with fixed id", payee.name()); 2122 ++problemCount; 2123 } 2124 } 2125 2126 // Fix the transactions 2127 MyMoneyTransactionFilter filter; 2128 filter.setReportAllSplits(false); 2129 const auto tList = d->m_storage->transactionList(filter); 2130 // Generate the list of interest accounts 2131 for (const auto& transaction : tList) { 2132 const auto splits = transaction.splits(); 2133 for (const auto& split : splits) { 2134 if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) 2135 interestAccounts[split.accountId()] = true; 2136 } 2137 } 2138 QSet<Account::Type> supportedAccountTypes; 2139 supportedAccountTypes << Account::Type::Checkings 2140 << Account::Type::Savings 2141 << Account::Type::Cash 2142 << Account::Type::CreditCard 2143 << Account::Type::Asset 2144 << Account::Type::Liability; 2145 QSet<QString> reportedUnsupportedAccounts; 2146 2147 for (const auto& transaction : tList) { 2148 MyMoneyTransaction t = transaction; 2149 bool tChanged = false; 2150 QDate accountOpeningDate; 2151 QStringList accountList; 2152 const auto splits = t.splits(); 2153 foreach (const auto split, splits) { 2154 bool sChanged = false; 2155 MyMoneySplit s = split; 2156 if (payeeConversionMap.find(split.payeeId()) != payeeConversionMap.end()) { 2157 s.setPayeeId(payeeConversionMap[s.payeeId()]); 2158 sChanged = true; 2159 rc << i18n(" * Payee id updated in split of transaction '%1'.", t.id()); 2160 ++problemCount; 2161 } 2162 2163 try { 2164 const auto acc = this->account(s.accountId()); 2165 // compute the newest opening date of all accounts involved in the transaction 2166 // in case the newest opening date is newer than the transaction post date, do one 2167 // of the following: 2168 // 2169 // a) for category and stock accounts: update the opening date of the account 2170 // b) for account types where the user cannot modify the opening date through 2171 // the UI issue a warning (for each account only once) 2172 // c) others will be caught later 2173 if (!acc.isIncomeExpense() && !acc.isInvest()) { 2174 if (acc.openingDate() > t.postDate()) { 2175 if (!accountOpeningDate.isValid() || acc.openingDate() > accountOpeningDate) { 2176 accountOpeningDate = acc.openingDate(); 2177 } 2178 accountList << this->accountToCategory(acc.id()); 2179 if (!supportedAccountTypes.contains(acc.accountType()) 2180 && !reportedUnsupportedAccounts.contains(acc.id())) { 2181 rc << i18n(" * Opening date of Account '%1' cannot be changed to support transaction '%2' post date.", 2182 this->accountToCategory(acc.id()), t.id()); 2183 reportedUnsupportedAccounts << acc.id(); 2184 ++unfixedCount; 2185 } 2186 } 2187 } else { 2188 if (acc.openingDate() > t.postDate()) { 2189 rc << i18n(" * Transaction '%1' post date '%2' is older than opening date '%4' of account '%3'.", 2190 t.id(), t.postDate().toString(Qt::ISODate), this->accountToCategory(acc.id()), acc.openingDate().toString(Qt::ISODate)); 2191 2192 rc << i18n(" Account opening date updated."); 2193 MyMoneyAccount newAcc = acc; 2194 newAcc.setOpeningDate(t.postDate()); 2195 this->modifyAccount(newAcc); 2196 ++problemCount; 2197 } 2198 } 2199 2200 // make sure, that shares and value have the same number if they 2201 // represent the same currency. 2202 if (t.commodity() == acc.currencyId() && s.shares().reduce() != s.value().reduce()) { 2203 // use the value as master if the transaction is balanced 2204 if (t.splitSum().isZero()) { 2205 s.setShares(s.value()); 2206 rc << i18n(" * shares set to value in split of transaction '%1'.", t.id()); 2207 } else { 2208 s.setValue(s.shares()); 2209 rc << i18n(" * value set to shares in split of transaction '%1'.", t.id()); 2210 } 2211 sChanged = true; 2212 ++problemCount; 2213 } 2214 } catch (const MyMoneyException &) { 2215 rc << i18n(" * Split %2 in transaction '%1' contains a reference to invalid account %3. Please fix manually.", t.id(), split.id(), split.accountId()); 2216 ++unfixedCount; 2217 } 2218 2219 // make sure the interest splits are marked correct as such 2220 if (interestAccounts.find(s.accountId()) != interestAccounts.end() 2221 && s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { 2222 s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); 2223 sChanged = true; 2224 rc << i18n(" * action marked as interest in split of transaction '%1'.", t.id()); 2225 ++problemCount; 2226 } 2227 2228 if (sChanged) { 2229 tChanged = true; 2230 t.modifySplit(s); 2231 } 2232 } 2233 2234 // make sure that the transaction's post date is valid 2235 if (!t.postDate().isValid()) { 2236 tChanged = true; 2237 t.setPostDate(t.entryDate().isValid() ? t.entryDate() : QDate::currentDate()); 2238 rc << i18n(" * Transaction '%1' has an invalid post date.", t.id()); 2239 rc << i18n(" The post date was updated to '%1'.", QLocale().toString(t.postDate(), QLocale::ShortFormat)); 2240 ++problemCount; 2241 } 2242 // check if the transaction's post date is after the opening date 2243 // of all accounts involved in the transaction. In case it is not, 2244 // issue a warning with the details about the transaction incl. 2245 // the account names and dates involved 2246 if (accountOpeningDate.isValid() && t.postDate() < accountOpeningDate) { 2247 QDate originalPostDate = t.postDate(); 2248 #if 0 2249 // for now we do not activate the logic to move the post date to a later 2250 // point in time. This could cause some severe trouble if you have lots 2251 // of ancient data collected with older versions of KMyMoney that did not 2252 // enforce certain conditions like we do now. 2253 t.setPostDate(accountOpeningDate); 2254 tChanged = true; 2255 // copy the price information for investments to the new date 2256 QList<MyMoneySplit>::const_iterator it_t; 2257 foreach (const auto split, t.splits()) { 2258 if ((split.action() != "Buy") && 2259 (split.action() != "Reinvest")) { 2260 continue; 2261 } 2262 QString id = split.accountId(); 2263 auto acc = this->account(id); 2264 MyMoneySecurity sec = this->security(acc.currencyId()); 2265 MyMoneyPrice price(acc.currencyId(), 2266 sec.tradingCurrency(), 2267 t.postDate(), 2268 split.price(), "Transaction"); 2269 this->addPrice(price); 2270 break; 2271 } 2272 #endif 2273 rc << i18n(" * Transaction '%1' has a post date '%2' before one of the referenced account's opening date.", t.id(), QLocale().toString(originalPostDate, QLocale::ShortFormat)); 2274 rc << i18n(" Referenced accounts: %1", accountList.join(",")); 2275 rc << i18n(" The post date was not updated to '%1'.", QLocale().toString(accountOpeningDate, QLocale::ShortFormat)); 2276 ++unfixedCount; 2277 } 2278 2279 if (tChanged) { 2280 d->m_storage->modifyTransaction(t); 2281 } 2282 } 2283 2284 // Fix the schedules 2285 QList<MyMoneySchedule> schList = scheduleList(); 2286 for (it_sch = schList.begin(); it_sch != schList.end(); ++it_sch) { 2287 MyMoneySchedule sch = (*it_sch); 2288 MyMoneyTransaction t = sch.transaction(); 2289 auto tChanged = false; 2290 foreach (const auto split, t.splits()) { 2291 MyMoneySplit s = split; 2292 bool sChanged = false; 2293 if (payeeConversionMap.find(split.payeeId()) != payeeConversionMap.end()) { 2294 s.setPayeeId(payeeConversionMap[s.payeeId()]); 2295 sChanged = true; 2296 rc << i18n(" * Payee id updated in split of schedule '%1'.", (*it_sch).name()); 2297 ++problemCount; 2298 } 2299 if (!split.value().isZero() && split.shares().isZero()) { 2300 s.setShares(s.value()); 2301 sChanged = true; 2302 rc << i18n(" * Split in scheduled transaction '%1' contained value != 0 and shares == 0.", (*it_sch).name()); 2303 rc << i18n(" Shares set to value."); 2304 ++problemCount; 2305 } 2306 2307 // make sure, we don't have a bankid stored with a split in a schedule 2308 if (!split.bankID().isEmpty()) { 2309 s.setBankID(QString()); 2310 sChanged = true; 2311 rc << i18n(" * Removed bankid from split in scheduled transaction '%1'.", (*it_sch).name()); 2312 ++problemCount; 2313 } 2314 2315 // make sure, that shares and value have the same number if they 2316 // represent the same currency. 2317 try { 2318 const auto acc = this->account(s.accountId()); 2319 if (t.commodity() == acc.currencyId() 2320 && s.shares().reduce() != s.value().reduce()) { 2321 // use the value as master if the transaction is balanced 2322 if (t.splitSum().isZero()) { 2323 s.setShares(s.value()); 2324 rc << i18n(" * shares set to value in split in schedule '%1'.", (*it_sch).name()); 2325 } else { 2326 s.setValue(s.shares()); 2327 rc << i18n(" * value set to shares in split in schedule '%1'.", (*it_sch).name()); 2328 } 2329 sChanged = true; 2330 ++problemCount; 2331 } 2332 } catch (const MyMoneyException &) { 2333 rc << i18n(" * Split %2 in schedule '%1' contains a reference to invalid account %3. Please fix manually.", (*it_sch).name(), split.id(), split.accountId()); 2334 ++unfixedCount; 2335 } 2336 if (sChanged) { 2337 t.modifySplit(s); 2338 tChanged = true; 2339 } 2340 } 2341 if (tChanged) { 2342 sch.setTransaction(t); 2343 d->m_storage->modifySchedule(sch); 2344 } 2345 } 2346 2347 // Fix the reports 2348 QList<MyMoneyReport> rList = reportList(); 2349 for (it_r = rList.begin(); it_r != rList.end(); ++it_r) { 2350 MyMoneyReport r = *it_r; 2351 QStringList payeeList; 2352 (*it_r).payees(payeeList); 2353 bool rChanged = false; 2354 for (auto it_payee = payeeList.begin(); it_payee != payeeList.end(); ++it_payee) { 2355 if (payeeConversionMap.find(*it_payee) != payeeConversionMap.end()) { 2356 rc << i18n(" * Payee id updated in report '%1'.", (*it_r).name()); 2357 ++problemCount; 2358 r.removeReference(*it_payee); 2359 r.addPayee(payeeConversionMap[*it_payee]); 2360 rChanged = true; 2361 } 2362 } 2363 if (rChanged) { 2364 d->m_storage->modifyReport(r); 2365 } 2366 } 2367 2368 // erase old payee ids 2369 QMap<QString, QString>::Iterator it_m; 2370 for (it_m = payeeConversionMap.begin(); it_m != payeeConversionMap.end(); ++it_m) { 2371 MyMoneyPayee payee = this->payee(it_m.key()); 2372 removePayee(payee); 2373 rc << i18n(" * Payee '%1' removed.", payee.id()); 2374 ++problemCount; 2375 } 2376 2377 //look for accounts which have currencies other than the base currency but no price on the opening date 2378 //all accounts using base currency are excluded, since that's the base used for foreign currency calculation 2379 //thus it is considered as always present 2380 //accounts that represent Income/Expense categories are also excluded as price is irrelevant for their 2381 //fake opening date since a forex rate is required for all multi-currency transactions 2382 2383 //get all currencies in use 2384 QStringList currencyList; 2385 QList<MyMoneyAccount> accountForeignCurrency; 2386 QList<MyMoneyAccount> accList; 2387 accountList(accList); 2388 QList<MyMoneyAccount>::const_iterator account_it; 2389 for (account_it = accList.constBegin(); account_it != accList.constEnd(); ++account_it) { 2390 MyMoneyAccount account = *account_it; 2391 if (!account.isIncomeExpense() 2392 && !currencyList.contains(account.currencyId()) 2393 && account.currencyId() != baseCurrency().id() 2394 && !account.currencyId().isEmpty()) { 2395 //add the currency and the account-currency pair 2396 currencyList.append(account.currencyId()); 2397 accountForeignCurrency.append(account); 2398 } 2399 } 2400 2401 MyMoneyPriceList pricesList = priceList(); 2402 QMap<MyMoneySecurityPair, QDate> securityPriceDate; 2403 2404 //get the first date of the price for each security 2405 MyMoneyPriceList::const_iterator prices_it; 2406 for (prices_it = pricesList.constBegin(); prices_it != pricesList.constEnd(); ++prices_it) { 2407 MyMoneyPrice firstPrice = (*((*prices_it).constBegin())); 2408 2409 //only check the price if the currency is in use 2410 if (currencyList.contains(firstPrice.from()) || currencyList.contains(firstPrice.to())) { 2411 //check the security in the from field 2412 //if it is there, check if it is older 2413 QPair<QString, QString> pricePair = qMakePair(firstPrice.from(), firstPrice.to()); 2414 securityPriceDate[pricePair] = firstPrice.date(); 2415 } 2416 } 2417 2418 //compare the dates with the opening dates of the accounts using each currency 2419 QList<MyMoneyAccount>::const_iterator accForeignList_it; 2420 bool firstInvProblem = true; 2421 for (accForeignList_it = accountForeignCurrency.constBegin(); accForeignList_it != accountForeignCurrency.constEnd(); ++accForeignList_it) { 2422 //setup the price pair correctly 2423 QPair<QString, QString> pricePair; 2424 //setup the reverse, which can also be used for rate conversion 2425 QPair<QString, QString> reversePricePair; 2426 if ((*accForeignList_it).isInvest()) { 2427 //if it is a stock, we have to search for a price from its stock to the currency of the account 2428 QString securityId = (*accForeignList_it).currencyId(); 2429 QString tradingCurrencyId = security(securityId).tradingCurrency(); 2430 pricePair = qMakePair(securityId, tradingCurrencyId); 2431 reversePricePair = qMakePair(tradingCurrencyId, securityId); 2432 } else { 2433 //if it is a regular account we search for a price from the currency of the account to the base currency 2434 QString currency = (*accForeignList_it).currencyId(); 2435 QString baseCurrencyId = baseCurrency().id(); 2436 pricePair = qMakePair(currency, baseCurrencyId); 2437 reversePricePair = qMakePair(baseCurrencyId, currency); 2438 } 2439 2440 //compare the first price with the opening date of the account 2441 if ((!securityPriceDate.contains(pricePair) || securityPriceDate.value(pricePair) > (*accForeignList_it).openingDate()) 2442 && (!securityPriceDate.contains(reversePricePair) || securityPriceDate.value(reversePricePair) > (*accForeignList_it).openingDate())) { 2443 if (firstInvProblem) { 2444 firstInvProblem = false; 2445 rc << i18n("* Potential problem with securities/currencies"); 2446 } 2447 QDate openingDate = (*accForeignList_it).openingDate(); 2448 MyMoneySecurity secError = security((*accForeignList_it).currencyId()); 2449 if (!(*accForeignList_it).isInvest()) { 2450 rc << i18n(" * The account '%1' in currency '%2' has no price set for the opening date '%3'.", (*accForeignList_it).name(), secError.name(), openingDate.toString(Qt::ISODate)); 2451 rc << i18n(" Please enter a price for the currency on or before the opening date."); 2452 } else { 2453 rc << i18n(" * The security '%1' has no price set for the opening date '%2'.", (*accForeignList_it).name(), openingDate.toString(Qt::ISODate)); 2454 rc << i18n(" Please enter a price for the security on or before the opening date."); 2455 } 2456 ++unfixedCount; 2457 } 2458 } 2459 2460 // Fix the budgets that somehow still reference invalid accounts 2461 QString problemBudget; 2462 QList<MyMoneyBudget> bList = budgetList(); 2463 for (QList<MyMoneyBudget>::const_iterator it_b = bList.constBegin(); it_b != bList.constEnd(); ++it_b) { 2464 MyMoneyBudget b = *it_b; 2465 QList<MyMoneyBudget::AccountGroup> baccounts = b.getaccounts(); 2466 bool bChanged = false; 2467 for (QList<MyMoneyBudget::AccountGroup>::const_iterator it_bacc = baccounts.constBegin(); it_bacc != baccounts.constEnd(); ++it_bacc) { 2468 try { 2469 account((*it_bacc).id()); 2470 } catch (const MyMoneyException &) { 2471 problemCount++; 2472 if (problemBudget != b.name()) { 2473 problemBudget = b.name(); 2474 rc << i18n("* Problem with budget '%1'", problemBudget); 2475 } 2476 rc << i18n(" * The account with id %1 referenced by the budget does not exist anymore.", (*it_bacc).id()); 2477 rc << i18n(" The account reference will be removed."); 2478 // remove the reference to the account 2479 b.removeReference((*it_bacc).id()); 2480 bChanged = true; 2481 } 2482 } 2483 if (bChanged) { 2484 d->m_storage->modifyBudget(b); 2485 } 2486 } 2487 2488 // add more checks here 2489 2490 if (problemCount == 0 && unfixedCount == 0) { 2491 rc << i18n("Finished: data is consistent."); 2492 } else { 2493 const QString problemsCorrected = i18np("%1 problem corrected.", "%1 problems corrected.", problemCount); 2494 const QString problemsRemaining = i18np("%1 problem still present.", "%1 problems still present.", unfixedCount); 2495 2496 rc << QString(); 2497 rc << i18nc("%1 is a string, e.g. 7 problems corrected; %2 is a string, e.g. 3 problems still present", "Finished: %1 %2", problemsCorrected, problemsRemaining); 2498 } 2499 return rc; 2500 } 2501 2502 QString MyMoneyFile::createCategory(const MyMoneyAccount& base, const QString& name) 2503 { 2504 d->checkTransaction(Q_FUNC_INFO); 2505 2506 MyMoneyAccount parent = base; 2507 QString categoryText; 2508 2509 if (base.id() != expense().id() && base.id() != income().id()) 2510 throw MYMONEYEXCEPTION_CSTRING("Invalid base category"); 2511 2512 QStringList subAccounts = name.split(AccountSeparator); 2513 QStringList::Iterator it; 2514 for (it = subAccounts.begin(); it != subAccounts.end(); ++it) { 2515 MyMoneyAccount categoryAccount; 2516 2517 categoryAccount.setName(*it); 2518 categoryAccount.setAccountType(base.accountType()); 2519 2520 if (it == subAccounts.begin()) 2521 categoryText += *it; 2522 else 2523 categoryText += (AccountSeparator + *it); 2524 2525 // Only create the account if it doesn't exist 2526 try { 2527 QString categoryId = categoryToAccount(categoryText); 2528 if (categoryId.isEmpty()) 2529 addAccount(categoryAccount, parent); 2530 else { 2531 categoryAccount = account(categoryId); 2532 } 2533 } catch (const MyMoneyException &e) { 2534 qDebug("Unable to add account %s, %s, %s: %s", 2535 qPrintable(categoryAccount.name()), 2536 qPrintable(parent.name()), 2537 qPrintable(categoryText), 2538 e.what()); 2539 } 2540 2541 parent = categoryAccount; 2542 } 2543 2544 return categoryToAccount(name); 2545 } 2546 2547 QString MyMoneyFile::checkCategory(const QString& name, const MyMoneyMoney& value, const MyMoneyMoney& value2) 2548 { 2549 QString accountId; 2550 MyMoneyAccount newAccount; 2551 bool found = true; 2552 2553 if (!name.isEmpty()) { 2554 // The category might be constructed with an arbitrary depth (number of 2555 // colon delimited fields). We try to find a parent account within this 2556 // hierarchy by searching the following sequence: 2557 // 2558 // aaaa:bbbb:cccc:ddddd 2559 // 2560 // 1. search aaaa:bbbb:cccc:dddd, create nothing 2561 // 2. search aaaa:bbbb:cccc , create dddd 2562 // 3. search aaaa:bbbb , create cccc:dddd 2563 // 4. search aaaa , create bbbb:cccc:dddd 2564 // 5. don't search , create aaaa:bbbb:cccc:dddd 2565 2566 newAccount.setName(name); 2567 QString accName; // part to be created (right side in above list) 2568 QString parent(name); // a possible parent part (left side in above list) 2569 do { 2570 accountId = categoryToAccount(parent); 2571 if (accountId.isEmpty()) { 2572 found = false; 2573 // prepare next step 2574 if (!accName.isEmpty()) 2575 accName.prepend(':'); 2576 accName.prepend(parent.section(':', -1)); 2577 newAccount.setName(accName); 2578 parent = parent.section(':', 0, -2); 2579 } else if (!accName.isEmpty()) { 2580 newAccount.setParentAccountId(accountId); 2581 } 2582 } while (!parent.isEmpty() && accountId.isEmpty()); 2583 2584 // if we did not find the category, 2585 // but have a name for it, we create it 2586 if (!found && !accName.isEmpty()) { 2587 MyMoneyAccount parentAccount; 2588 if (newAccount.parentAccountId().isEmpty()) { 2589 if (!value.isNegative() && value2.isNegative()) 2590 parentAccount = income(); 2591 else 2592 parentAccount = expense(); 2593 } else { 2594 parentAccount = account(newAccount.parentAccountId()); 2595 } 2596 newAccount.setAccountType((!value.isNegative() && value2.isNegative()) ? Account::Type::Income : Account::Type::Expense); 2597 MyMoneyAccount brokerage; 2598 // clear out the parent id, because createAccount() does not like that 2599 newAccount.setParentAccountId(QString()); 2600 createAccount(newAccount, parentAccount, brokerage, MyMoneyMoney()); 2601 accountId = newAccount.id(); 2602 } 2603 } 2604 2605 return accountId; 2606 } 2607 2608 void MyMoneyFile::addSecurity(MyMoneySecurity& security) 2609 { 2610 d->checkTransaction(Q_FUNC_INFO); 2611 2612 d->m_storage->addSecurity(security); 2613 d->m_changeSet += MyMoneyNotification(File::Mode::Add, security); 2614 } 2615 2616 void MyMoneyFile::modifySecurity(const MyMoneySecurity& security) 2617 { 2618 d->checkTransaction(Q_FUNC_INFO); 2619 2620 d->m_storage->modifySecurity(security); 2621 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, security); 2622 } 2623 2624 void MyMoneyFile::removeSecurity(const MyMoneySecurity& security) 2625 { 2626 d->checkTransaction(Q_FUNC_INFO); 2627 2628 // FIXME check that security is not referenced by other object 2629 2630 d->m_storage->removeSecurity(security); 2631 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, security); 2632 } 2633 2634 MyMoneySecurity MyMoneyFile::security(const QString& id) const 2635 { 2636 if (Q_UNLIKELY(id.isEmpty())) 2637 return baseCurrency(); 2638 2639 return d->m_storage->security(id); 2640 } 2641 2642 QList<MyMoneySecurity> MyMoneyFile::securityList() const 2643 { 2644 d->checkStorage(); 2645 2646 return d->m_storage->securityList(); 2647 } 2648 2649 void MyMoneyFile::addCurrency(const MyMoneySecurity& currency) 2650 { 2651 d->checkTransaction(Q_FUNC_INFO); 2652 2653 d->m_storage->addCurrency(currency); 2654 d->m_changeSet += MyMoneyNotification(File::Mode::Add, currency); 2655 } 2656 2657 void MyMoneyFile::modifyCurrency(const MyMoneySecurity& currency) 2658 { 2659 d->checkTransaction(Q_FUNC_INFO); 2660 2661 // force reload of base currency object 2662 if (currency.id() == d->m_baseCurrency.id()) 2663 d->m_baseCurrency.clearId(); 2664 2665 d->m_storage->modifyCurrency(currency); 2666 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, currency); 2667 } 2668 2669 void MyMoneyFile::removeCurrency(const MyMoneySecurity& currency) 2670 { 2671 d->checkTransaction(Q_FUNC_INFO); 2672 2673 if (currency.id() == d->m_baseCurrency.id()) 2674 throw MYMONEYEXCEPTION_CSTRING("Cannot delete base currency."); 2675 2676 // FIXME check that security is not referenced by other object 2677 2678 d->m_storage->removeCurrency(currency); 2679 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, currency); 2680 } 2681 2682 MyMoneySecurity MyMoneyFile::currency(const QString& id) const 2683 { 2684 if (id.isEmpty()) 2685 return baseCurrency(); 2686 2687 try { 2688 const auto currency = d->m_storage->currency(id); 2689 if (currency.id().isEmpty()) 2690 throw MYMONEYEXCEPTION(QString::fromLatin1("Currency '%1' not found.").arg(id)); 2691 2692 return currency; 2693 2694 } catch (const MyMoneyException &) { 2695 const auto security = d->m_storage->security(id); 2696 if (security.id().isEmpty()) { 2697 throw MYMONEYEXCEPTION(QString::fromLatin1("Security '%1' not found.").arg(id)); 2698 } 2699 return security; 2700 } 2701 } 2702 2703 QMap<MyMoneySecurity, MyMoneyPrice> MyMoneyFile::ancientCurrencies() const 2704 { 2705 QMap<MyMoneySecurity, MyMoneyPrice> ancientCurrencies; 2706 2707 ancientCurrencies.insert(MyMoneySecurity("ATS", i18n("Austrian Schilling"), QString::fromUtf8("ÖS")), MyMoneyPrice("ATS", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 137603), QLatin1Literal("KMyMoney"))); 2708 ancientCurrencies.insert(MyMoneySecurity("DEM", i18n("German Mark"), "DM"), MyMoneyPrice("ATS", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 195583), QLatin1Literal("KMyMoney"))); 2709 ancientCurrencies.insert(MyMoneySecurity("FRF", i18n("French Franc"), "FF"), MyMoneyPrice("FRF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 655957), QLatin1Literal("KMyMoney"))); 2710 ancientCurrencies.insert(MyMoneySecurity("ITL", i18n("Italian Lira"), QChar(0x20A4)), MyMoneyPrice("ITL", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100, 193627), QLatin1Literal("KMyMoney"))); 2711 ancientCurrencies.insert(MyMoneySecurity("ESP", i18n("Spanish Peseta"), QString()), MyMoneyPrice("ESP", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000, 166386), QLatin1Literal("KMyMoney"))); 2712 ancientCurrencies.insert(MyMoneySecurity("NLG", i18n("Dutch Guilder"), QString()), MyMoneyPrice("NLG", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 220371), QLatin1Literal("KMyMoney"))); 2713 ancientCurrencies.insert(MyMoneySecurity("BEF", i18n("Belgian Franc"), "Fr"), MyMoneyPrice("BEF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 403399), QLatin1Literal("KMyMoney"))); 2714 ancientCurrencies.insert(MyMoneySecurity("LUF", i18n("Luxembourg Franc"), "Fr"), MyMoneyPrice("LUF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 403399), QLatin1Literal("KMyMoney"))); 2715 ancientCurrencies.insert(MyMoneySecurity("PTE", i18n("Portuguese Escudo"), QString()), MyMoneyPrice("PTE", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000, 200482), QLatin1Literal("KMyMoney"))); 2716 ancientCurrencies.insert(MyMoneySecurity("IEP", i18n("Irish Pound"), QChar(0x00A3)), MyMoneyPrice("IEP", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000000, 787564), QLatin1Literal("KMyMoney"))); 2717 ancientCurrencies.insert(MyMoneySecurity("FIM", i18n("Finnish Markka"), QString()), MyMoneyPrice("FIM", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 594573), QLatin1Literal("KMyMoney"))); 2718 ancientCurrencies.insert(MyMoneySecurity("GRD", i18n("Greek Drachma"), QChar(0x20AF)), MyMoneyPrice("GRD", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100, 34075), QLatin1Literal("KMyMoney"))); 2719 2720 // https://en.wikipedia.org/wiki/Bulgarian_lev 2721 ancientCurrencies.insert(MyMoneySecurity("BGL", i18n("Bulgarian Lev"), "BGL"), MyMoneyPrice("BGL", "BGN", QDate(1999, 7, 5), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); 2722 2723 ancientCurrencies.insert(MyMoneySecurity("ROL", i18n("Romanian Leu"), "ROL"), MyMoneyPrice("ROL", "RON", QDate(2005, 6, 30), MyMoneyMoney(1, 10000), QLatin1Literal("KMyMoney"))); 2724 2725 ancientCurrencies.insert(MyMoneySecurity("RUR", i18n("Russian Ruble (old)"), "RUR"), MyMoneyPrice("RUR", "RUB", QDate(1998, 1, 1), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); 2726 2727 ancientCurrencies.insert(MyMoneySecurity("SIT", i18n("Slovenian Tolar"), "SIT"), MyMoneyPrice("SIT", "EUR", QDate(2006, 12, 31), MyMoneyMoney(1, 23964), QLatin1Literal("KMyMoney"))); 2728 2729 // Source: https://en.wikipedia.org/wiki/Turkish_lira 2730 ancientCurrencies.insert(MyMoneySecurity("TRL", i18n("Turkish Lira (old)"), "TL"), MyMoneyPrice("TRL", "TRY", QDate(2004, 12, 31), MyMoneyMoney(1, 1000000), QLatin1Literal("KMyMoney"))); 2731 2732 // Source: https://www.focus.de/finanzen/news/malta-und-zypern_aid_66058.html 2733 ancientCurrencies.insert(MyMoneySecurity("MTL", i18n("Maltese Lira"), "MTL"), MyMoneyPrice("MTL", "EUR", QDate(2008, 1, 1), MyMoneyMoney(429300, 1000000), QLatin1Literal("KMyMoney"))); 2734 ancientCurrencies.insert(MyMoneySecurity("CYP", i18n("Cyprus Pound"), QString("C%1").arg(QChar(0x00A3))), MyMoneyPrice("CYP", "EUR", QDate(2008, 1, 1), MyMoneyMoney(585274, 1000000), QLatin1Literal("KMyMoney"))); 2735 2736 // Source: https://www.focus.de/finanzen/news/waehrungszone-slowakei-ist-neuer-euro-staat_aid_359025.html 2737 ancientCurrencies.insert(MyMoneySecurity("SKK", i18n("Slovak Koruna"), "SKK"), MyMoneyPrice("SKK", "EUR", QDate(2008, 12, 31), MyMoneyMoney(1000, 30126), QLatin1Literal("KMyMoney"))); 2738 2739 // Source: https://en.wikipedia.org/wiki/Mozambican_metical 2740 ancientCurrencies.insert(MyMoneySecurity("MZM", i18n("Mozambique Metical"), "MT"), MyMoneyPrice("MZM", "MZN", QDate(2006, 7, 1), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); 2741 2742 // Source https://en.wikipedia.org/wiki/Azerbaijani_manat 2743 ancientCurrencies.insert(MyMoneySecurity("AZM", i18n("Azerbaijani Manat"), "m."), MyMoneyPrice("AZM", "AZN", QDate(2006, 1, 1), MyMoneyMoney(1, 5000), QLatin1Literal("KMyMoney"))); 2744 2745 // Source: https://en.wikipedia.org/wiki/Litas 2746 ancientCurrencies.insert(MyMoneySecurity("LTL", i18n("Lithuanian Litas"), "Lt"), MyMoneyPrice("LTL", "EUR", QDate(2015, 1, 1), MyMoneyMoney(100000, 345280), QLatin1Literal("KMyMoney"))); 2747 2748 // Source: https://en.wikipedia.org/wiki/Belarusian_ruble 2749 ancientCurrencies.insert(MyMoneySecurity("BYR", i18n("Belarusian Ruble (old)"), "BYR"), MyMoneyPrice("BYR", "BYN", QDate(2016, 7, 1), MyMoneyMoney(1, 10000), QLatin1Literal("KMyMoney"))); 2750 2751 // Source: https://en.wikipedia.org/wiki/Zambian_kwacha, triggered by b.k.o ticket #425530 2752 ancientCurrencies.insert(MyMoneySecurity("ZMK", i18n("Zambian Kwacha (old)"), "K"), MyMoneyPrice("ZMK", "ZMW", QDate(2013, 1, 1), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); 2753 2754 // Source: https://www.ecb.europa.eu/press/pr/date/2022/html/ecb.pr220712~b97dd38de3.en.html 2755 ancientCurrencies.insert(MyMoneySecurity("HRK", i18n("Croatian Kuna")), 2756 MyMoneyPrice("HRK", "EUR", QDate(2023, 1, 1), MyMoneyMoney(100000, 753450), QLatin1Literal("KMyMoney"))); 2757 2758 return ancientCurrencies; 2759 } 2760 2761 QList<MyMoneySecurity> MyMoneyFile::availableCurrencyList() const 2762 { 2763 QList<MyMoneySecurity> currencyList; 2764 currencyList.append(MyMoneySecurity("AFA", i18n("Afghanistan Afghani"))); 2765 currencyList.append(MyMoneySecurity("ALL", i18n("Albanian Lek"))); 2766 currencyList.append(MyMoneySecurity("ANG", i18n("Netherland Antillian Guilder"))); 2767 currencyList.append(MyMoneySecurity("DZD", i18n("Algerian Dinar"))); 2768 currencyList.append(MyMoneySecurity("ADF", i18n("Andorran Franc"))); 2769 currencyList.append(MyMoneySecurity("ADP", i18n("Andorran Peseta"))); 2770 currencyList.append(MyMoneySecurity("AOA", i18n("Angolan Kwanza"), "Kz")); 2771 currencyList.append(MyMoneySecurity("ARS", i18n("Argentine Peso"), "$")); 2772 currencyList.append(MyMoneySecurity("AWG", i18n("Aruban Florin"))); 2773 currencyList.append(MyMoneySecurity("AUD", i18n("Australian Dollar"), "$")); 2774 currencyList.append(MyMoneySecurity("AZN", i18n("Azerbaijani Manat"), "m.")); 2775 currencyList.append(MyMoneySecurity("BSD", i18n("Bahamian Dollar"), "$")); 2776 currencyList.append(MyMoneySecurity("BHD", i18n("Bahraini Dinar"), "BHD", 1000)); 2777 currencyList.append(MyMoneySecurity("BDT", i18n("Bangladeshi Taka"))); 2778 currencyList.append(MyMoneySecurity("BBD", i18n("Barbados Dollar"), "$")); 2779 currencyList.append(MyMoneySecurity("BTC", i18n("Bitcoin"), "BTC", 100000000, 100000000)); 2780 currencyList.append(MyMoneySecurity("BYN", i18n("Belarusian Ruble"), "Br")); 2781 currencyList.append(MyMoneySecurity("BZD", i18n("Belize Dollar"), "$")); 2782 currencyList.append(MyMoneySecurity("BMD", i18n("Bermudian Dollar"), "$")); 2783 currencyList.append(MyMoneySecurity("BTN", i18n("Bhutan Ngultrum"))); 2784 currencyList.append(MyMoneySecurity("BOB", i18n("Bolivian Boliviano"))); 2785 currencyList.append(MyMoneySecurity("BAM", i18n("Bosnian Convertible Mark"))); 2786 currencyList.append(MyMoneySecurity("BWP", i18n("Botswana Pula"))); 2787 currencyList.append(MyMoneySecurity("BRL", i18n("Brazilian Real"), "R$")); 2788 currencyList.append(MyMoneySecurity("GBP", i18n("British Pound"), QChar(0x00A3))); 2789 currencyList.append(MyMoneySecurity("BND", i18n("Brunei Dollar"), "$")); 2790 currencyList.append(MyMoneySecurity("BGN", i18n("Bulgarian Lev (new)"))); 2791 currencyList.append(MyMoneySecurity("BIF", i18n("Burundi Franc"))); 2792 currencyList.append(MyMoneySecurity("XAF", i18n("CFA Franc BEAC"))); 2793 currencyList.append(MyMoneySecurity("XOF", i18n("CFA Franc BCEAO"))); 2794 currencyList.append(MyMoneySecurity("XPF", i18n("CFP Franc Pacifique"), "F", 1, 100)); 2795 currencyList.append(MyMoneySecurity("KHR", i18n("Cambodia Riel"))); 2796 currencyList.append(MyMoneySecurity("CAD", i18n("Canadian Dollar"), "$")); 2797 currencyList.append(MyMoneySecurity("CVE", i18n("Cape Verde Escudo"))); 2798 currencyList.append(MyMoneySecurity("KYD", i18n("Cayman Islands Dollar"), "$")); 2799 currencyList.append(MyMoneySecurity("CLP", i18n("Chilean Peso"))); 2800 currencyList.append(MyMoneySecurity("CNY", i18n("Chinese Yuan Renminbi"))); 2801 currencyList.append(MyMoneySecurity("COP", i18n("Colombian Peso"))); 2802 currencyList.append(MyMoneySecurity("KMF", i18n("Comoros Franc"))); 2803 currencyList.append(MyMoneySecurity("CRC", i18n("Costa Rican Colon"), QChar(0x20A1))); 2804 currencyList.append(MyMoneySecurity("CUP", i18n("Cuban Peso"))); 2805 currencyList.append(MyMoneySecurity("CUC", i18n("Cuban Convertible Peso"))); 2806 currencyList.append(MyMoneySecurity("CZK", i18n("Czech Koruna"))); 2807 currencyList.append(MyMoneySecurity("DKK", i18n("Danish Krone"), "kr")); 2808 currencyList.append(MyMoneySecurity("DJF", i18n("Djibouti Franc"))); 2809 currencyList.append(MyMoneySecurity("DOP", i18n("Dominican Peso"))); 2810 currencyList.append(MyMoneySecurity("XCD", i18n("East Caribbean Dollar"), "$")); 2811 currencyList.append(MyMoneySecurity("EGP", i18n("Egyptian Pound"), QChar(0x00A3))); 2812 currencyList.append(MyMoneySecurity("SVC", i18n("El Salvador Colon"))); 2813 currencyList.append(MyMoneySecurity("ERN", i18n("Eritrean Nakfa"))); 2814 currencyList.append(MyMoneySecurity("EEK", i18n("Estonian Kroon"))); 2815 currencyList.append(MyMoneySecurity("ETB", i18n("Ethiopian Birr"))); 2816 currencyList.append(MyMoneySecurity("EUR", i18n("Euro"), QChar(0x20ac))); 2817 currencyList.append(MyMoneySecurity("FKP", i18n("Falkland Islands Pound"), QChar(0x00A3))); 2818 currencyList.append(MyMoneySecurity("FJD", i18n("Fiji Dollar"), "$")); 2819 currencyList.append(MyMoneySecurity("GMD", i18n("Gambian Dalasi"))); 2820 currencyList.append(MyMoneySecurity("GEL", i18n("Georgian Lari"))); 2821 currencyList.append(MyMoneySecurity("GHC", i18n("Ghanaian Cedi"))); 2822 currencyList.append(MyMoneySecurity("GIP", i18n("Gibraltar Pound"), QChar(0x00A3))); 2823 currencyList.append(MyMoneySecurity("GTQ", i18n("Guatemalan Quetzal"))); 2824 currencyList.append(MyMoneySecurity("GWP", i18n("Guinea-Bissau Peso"))); 2825 currencyList.append(MyMoneySecurity("GYD", i18n("Guyanan Dollar"), "$")); 2826 currencyList.append(MyMoneySecurity("HTG", i18n("Haitian Gourde"))); 2827 currencyList.append(MyMoneySecurity("HNL", i18n("Honduran Lempira"))); 2828 currencyList.append(MyMoneySecurity("HKD", i18n("Hong Kong Dollar"), "$")); 2829 currencyList.append(MyMoneySecurity("HUF", i18n("Hungarian Forint"), "HUF", 1, 100)); 2830 currencyList.append(MyMoneySecurity("ISK", i18n("Iceland Krona"))); 2831 currencyList.append(MyMoneySecurity("INR", i18n("Indian Rupee"), QChar(0x20B9))); 2832 currencyList.append(MyMoneySecurity("IDR", i18n("Indonesian Rupiah"), "IDR", 1, 0, 10)); 2833 currencyList.append(MyMoneySecurity("IRR", i18n("Iranian Rial"), "IRR", 1)); 2834 currencyList.append(MyMoneySecurity("IQD", i18n("Iraqi Dinar"), "IQD", 1000)); 2835 currencyList.append(MyMoneySecurity("ILS", i18n("Israeli New Shekel"), QChar(0x20AA))); 2836 currencyList.append(MyMoneySecurity("JMD", i18n("Jamaican Dollar"), "$")); 2837 currencyList.append(MyMoneySecurity("JPY", i18n("Japanese Yen"), QChar(0x00A5), 1)); 2838 currencyList.append(MyMoneySecurity("JOD", i18n("Jordanian Dinar"), "JOD", 1000)); 2839 currencyList.append(MyMoneySecurity("KZT", i18n("Kazakhstan Tenge"))); 2840 currencyList.append(MyMoneySecurity("KES", i18n("Kenyan Shilling"))); 2841 currencyList.append(MyMoneySecurity("KWD", i18n("Kuwaiti Dinar"), "KWD", 1000)); 2842 currencyList.append(MyMoneySecurity("KGS", i18n("Kyrgyzstan Som"))); 2843 currencyList.append(MyMoneySecurity("LAK", i18n("Laos Kip"), QChar(0x20AD))); 2844 currencyList.append(MyMoneySecurity("LVL", i18n("Latvian Lats"))); 2845 currencyList.append(MyMoneySecurity("LBP", i18n("Lebanese Pound"), QChar(0x00A3))); 2846 currencyList.append(MyMoneySecurity("LSL", i18n("Lesotho Loti"))); 2847 currencyList.append(MyMoneySecurity("LRD", i18n("Liberian Dollar"), "$")); 2848 currencyList.append(MyMoneySecurity("LYD", i18n("Libyan Dinar"), "LYD", 1000)); 2849 currencyList.append(MyMoneySecurity("MOP", i18n("Macau Pataca"))); 2850 currencyList.append(MyMoneySecurity("MKD", i18n("Macedonian Denar"))); 2851 currencyList.append(MyMoneySecurity("MGF", i18n("Malagasy Franc"), "MGF", 500)); 2852 currencyList.append(MyMoneySecurity("MWK", i18n("Malawi Kwacha"))); 2853 currencyList.append(MyMoneySecurity("MYR", i18n("Malaysian Ringgit"))); 2854 currencyList.append(MyMoneySecurity("MVR", i18n("Maldive Rufiyaa"))); 2855 currencyList.append(MyMoneySecurity("MLF", i18n("Mali Republic Franc"))); 2856 currencyList.append(MyMoneySecurity("MRO", i18n("Mauritanian Ouguiya"), "MRO", 5)); 2857 currencyList.append(MyMoneySecurity("MUR", i18n("Mauritius Rupee"))); 2858 currencyList.append(MyMoneySecurity("MXN", i18n("Mexican Peso"), "$")); 2859 currencyList.append(MyMoneySecurity("MDL", i18n("Moldavian Leu"))); 2860 currencyList.append(MyMoneySecurity("MNT", i18n("Mongolian Tugrik"), QChar(0x20AE))); 2861 currencyList.append(MyMoneySecurity("MAD", i18n("Moroccan Dirham"))); 2862 currencyList.append(MyMoneySecurity("MZN", i18n("Mozambique Metical"), "MT")); 2863 currencyList.append(MyMoneySecurity("MMK", i18n("Myanmar Kyat"))); 2864 currencyList.append(MyMoneySecurity("NAD", i18n("Namibian Dollar"), "$")); 2865 currencyList.append(MyMoneySecurity("NPR", i18n("Nepalese Rupee"))); 2866 currencyList.append(MyMoneySecurity("NZD", i18n("New Zealand Dollar"), "$")); 2867 currencyList.append(MyMoneySecurity("NIC", i18n("Nicaraguan Cordoba Oro"))); 2868 currencyList.append(MyMoneySecurity("NGN", i18n("Nigerian Naira"), QChar(0x20A6))); 2869 currencyList.append(MyMoneySecurity("KPW", i18n("North Korean Won"), QChar(0x20A9))); 2870 currencyList.append(MyMoneySecurity("NOK", i18n("Norwegian Kroner"), "kr")); 2871 currencyList.append(MyMoneySecurity("OMR", i18n("Omani Rial"), "OMR", 1000)); 2872 currencyList.append(MyMoneySecurity("PKR", i18n("Pakistan Rupee"))); 2873 currencyList.append(MyMoneySecurity("PAB", i18n("Panamanian Balboa"))); 2874 currencyList.append(MyMoneySecurity("PGK", i18n("Papua New Guinea Kina"))); 2875 currencyList.append(MyMoneySecurity("PYG", i18n("Paraguay Guarani"))); 2876 currencyList.append(MyMoneySecurity("PEN", i18n("Peruvian Nuevo Sol"))); 2877 currencyList.append(MyMoneySecurity("PHP", i18n("Philippine Peso"), QChar(0x20B1))); 2878 currencyList.append(MyMoneySecurity("PLN", i18n("Polish Zloty"))); 2879 currencyList.append(MyMoneySecurity("QAR", i18n("Qatari Rial"))); 2880 currencyList.append(MyMoneySecurity("RON", i18n("Romanian Leu (new)"))); 2881 currencyList.append(MyMoneySecurity("RUB", i18n("Russian Ruble"))); 2882 currencyList.append(MyMoneySecurity("RWF", i18n("Rwanda Franc"))); 2883 currencyList.append(MyMoneySecurity("WST", i18n("Samoan Tala"))); 2884 currencyList.append(MyMoneySecurity("STD", i18n("Sao Tome and Principe Dobra"))); 2885 currencyList.append(MyMoneySecurity("SAR", i18n("Saudi Riyal"))); 2886 currencyList.append(MyMoneySecurity("RSD", i18n("Serbian Dinar"))); 2887 currencyList.append(MyMoneySecurity("SCR", i18n("Seychelles Rupee"))); 2888 currencyList.append(MyMoneySecurity("SLL", i18n("Sierra Leone Leone"))); 2889 currencyList.append(MyMoneySecurity("SGD", i18n("Singapore Dollar"), "$")); 2890 currencyList.append(MyMoneySecurity("SBD", i18n("Solomon Islands Dollar"), "$")); 2891 currencyList.append(MyMoneySecurity("SOS", i18n("Somali Shilling"))); 2892 currencyList.append(MyMoneySecurity("ZAR", i18n("South African Rand"))); 2893 currencyList.append(MyMoneySecurity("KRW", i18n("South Korean Won"), QChar(0x20A9), 1)); 2894 currencyList.append(MyMoneySecurity("LKR", i18n("Sri Lanka Rupee"))); 2895 currencyList.append(MyMoneySecurity("SHP", i18n("St. Helena Pound"), QChar(0x00A3))); 2896 currencyList.append(MyMoneySecurity("SDD", i18n("Sudanese Dinar"))); 2897 currencyList.append(MyMoneySecurity("SRG", i18n("Suriname Guilder"))); 2898 currencyList.append(MyMoneySecurity("SZL", i18n("Swaziland Lilangeni"))); 2899 currencyList.append(MyMoneySecurity("SEK", i18n("Swedish Krona"))); 2900 currencyList.append(MyMoneySecurity("CHF", i18n("Swiss Franc"), "SFr")); 2901 currencyList.append(MyMoneySecurity("SYP", i18n("Syrian Pound"), QChar(0x00A3))); 2902 currencyList.append(MyMoneySecurity("TWD", i18n("Taiwan Dollar"), "$")); 2903 currencyList.append(MyMoneySecurity("TJS", i18n("Tajikistan Somoni"))); 2904 currencyList.append(MyMoneySecurity("TZS", i18n("Tanzanian Shilling"))); 2905 currencyList.append(MyMoneySecurity("THB", i18n("Thai Baht"), QChar(0x0E3F))); 2906 currencyList.append(MyMoneySecurity("TOP", i18n("Tongan Pa'anga"))); 2907 currencyList.append(MyMoneySecurity("TTD", i18n("Trinidad and Tobago Dollar"), "$")); 2908 currencyList.append(MyMoneySecurity("TND", i18n("Tunisian Dinar"), "TND", 1000)); 2909 currencyList.append(MyMoneySecurity("TRY", i18n("Turkish Lira"), QChar(0x20BA))); 2910 currencyList.append(MyMoneySecurity("TMM", i18n("Turkmenistan Manat"))); 2911 currencyList.append(MyMoneySecurity("USD", i18n("US Dollar"), "$")); 2912 currencyList.append(MyMoneySecurity("UGX", i18n("Uganda Shilling"))); 2913 currencyList.append(MyMoneySecurity("UAH", i18n("Ukraine Hryvnia"))); 2914 currencyList.append(MyMoneySecurity("CLF", i18n("Unidad de Fometo"))); 2915 currencyList.append(MyMoneySecurity("AED", i18n("United Arab Emirates Dirham"))); 2916 currencyList.append(MyMoneySecurity("UYU", i18n("Uruguayan Peso"))); 2917 currencyList.append(MyMoneySecurity("UZS", i18n("Uzbekistani Sum"))); 2918 currencyList.append(MyMoneySecurity("VUV", i18n("Vanuatu Vatu"))); 2919 currencyList.append(MyMoneySecurity("VEB", i18n("Venezuelan Bolivar"))); 2920 currencyList.append(MyMoneySecurity("VND", i18n("Vietnamese Dong"), QChar(0x20AB))); 2921 currencyList.append(MyMoneySecurity("ZMW", i18n("Zambian Kwacha"), "K")); 2922 currencyList.append(MyMoneySecurity("ZWD", i18n("Zimbabwe Dollar"), "$")); 2923 2924 currencyList.append(ancientCurrencies().keys()); 2925 2926 // sort the currencies ... 2927 qSort(currencyList.begin(), currencyList.end(), 2928 [] (const MyMoneySecurity& c1, const MyMoneySecurity& c2) 2929 { 2930 return c1.name().compare(c2.name()) < 0; 2931 }); 2932 2933 // ... and add a few precious metals at the ned 2934 currencyList.append(MyMoneySecurity("XAU", i18n("Gold"), "XAU", 1000000)); 2935 currencyList.append(MyMoneySecurity("XPD", i18n("Palladium"), "XPD", 1000000)); 2936 currencyList.append(MyMoneySecurity("XPT", i18n("Platinum"), "XPT", 1000000)); 2937 currencyList.append(MyMoneySecurity("XAG", i18n("Silver"), "XAG", 1000000)); 2938 2939 return currencyList; 2940 } 2941 2942 QList<MyMoneySecurity> MyMoneyFile::currencyList() const 2943 { 2944 d->checkStorage(); 2945 2946 return d->m_storage->currencyList(); 2947 } 2948 2949 QString MyMoneyFile::foreignCurrency(const QString& first, const QString& second) const 2950 { 2951 if (baseCurrency().id() == second) 2952 return first; 2953 return second; 2954 } 2955 2956 MyMoneySecurity MyMoneyFile::baseCurrency() const 2957 { 2958 if (d->m_baseCurrency.id().isEmpty()) { 2959 QString id = QString(value("kmm-baseCurrency")); 2960 if (!id.isEmpty()) 2961 d->m_baseCurrency = currency(id); 2962 } 2963 2964 return d->m_baseCurrency; 2965 } 2966 2967 void MyMoneyFile::setBaseCurrency(const MyMoneySecurity& curr) 2968 { 2969 // make sure the currency exists 2970 MyMoneySecurity c = currency(curr.id()); 2971 2972 if (c.id() != d->m_baseCurrency.id()) { 2973 setValue("kmm-baseCurrency", curr.id()); 2974 // force reload of base currency cache 2975 d->m_baseCurrency = MyMoneySecurity(); 2976 emit dataChanged(); 2977 } 2978 } 2979 2980 void MyMoneyFile::addPrice(const MyMoneyPrice& price) 2981 { 2982 if (price.rate(QString()).isZero()) 2983 return; 2984 2985 d->checkTransaction(Q_FUNC_INFO); 2986 2987 auto adjustedPrice(price); 2988 const auto fromSecurity = security(price.from()); 2989 const auto toSecurity = security(price.to()); 2990 // if fromSecurity is a currency and toSecurity is not a currency 2991 // we swap both of them so that it is a security -> currency 2992 // conversion 2993 if (fromSecurity.isCurrency() && !toSecurity.isCurrency()) { 2994 adjustedPrice = MyMoneyPrice(price.to(), 2995 price.from(), 2996 price.date(), 2997 price.rate(price.from()), 2998 price.source()); 2999 } 3000 // store the account's which are affected by this price regarding their value 3001 d->priceChanged(*this, adjustedPrice); 3002 d->m_storage->addPrice(adjustedPrice); 3003 } 3004 3005 void MyMoneyFile::removePrice(const MyMoneyPrice& price) 3006 { 3007 d->checkTransaction(Q_FUNC_INFO); 3008 3009 // store the account's which are affected by this price regarding their value 3010 d->priceChanged(*this, price); 3011 d->m_storage->removePrice(price); 3012 } 3013 3014 MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const 3015 { 3016 d->checkStorage(); 3017 3018 QString to(toId); 3019 if (to.isEmpty()) 3020 to = value("kmm-baseCurrency"); 3021 // if some id is missing, we can return an empty price object 3022 if (fromId.isEmpty() || to.isEmpty()) 3023 return MyMoneyPrice(); 3024 3025 // we don't search our tables if someone asks stupid stuff 3026 if (fromId == toId) { 3027 return MyMoneyPrice(fromId, toId, date, MyMoneyMoney::ONE, "KMyMoney"); 3028 } 3029 3030 // if not asking for exact date, try to find the exact date match first, 3031 // either the requested price or its reciprocal value. If unsuccessful, it will move 3032 // on and look for prices of previous dates 3033 MyMoneyPrice rc = d->m_storage->price(fromId, to, date, true); 3034 if (!rc.isValid()) { 3035 // not found, search 'to-from' rate and use reciprocal value 3036 rc = d->m_storage->price(to, fromId, date, true); 3037 3038 // not found, search previous dates, if exact date is not needed 3039 if (!exactDate && !rc.isValid()) { 3040 // search 'from-to' and 'to-from', select the most recent one 3041 MyMoneyPrice fromPrice = d->m_storage->price(fromId, to, date, exactDate); 3042 MyMoneyPrice toPrice = d->m_storage->price(to, fromId, date, exactDate); 3043 3044 // check first whether both prices are valid 3045 if (fromPrice.isValid() && toPrice.isValid()) { 3046 if (fromPrice.date() >= toPrice.date()) { 3047 // if 'from-to' is newer or the same date, prefer that one 3048 rc = fromPrice; 3049 } else { 3050 // otherwise, use the reciprocal price 3051 rc = toPrice; 3052 } 3053 } else if (fromPrice.isValid()) { // check if any of the prices is valid, return that one 3054 rc = fromPrice; 3055 } else if (toPrice.isValid()) { 3056 rc = toPrice; 3057 } 3058 } 3059 } 3060 return rc; 3061 } 3062 3063 MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId) const 3064 { 3065 return price(fromId, toId, QDate::currentDate(), false); 3066 } 3067 3068 MyMoneyPrice MyMoneyFile::price(const QString& fromId) const 3069 { 3070 return price(fromId, QString(), QDate::currentDate(), false); 3071 } 3072 3073 MyMoneyPriceList MyMoneyFile::priceList() const 3074 { 3075 d->checkStorage(); 3076 3077 return d->m_storage->priceList(); 3078 } 3079 3080 bool MyMoneyFile::hasAccount(const QString& id, const QString& name) const 3081 { 3082 const auto accounts = account(id).accountList(); 3083 for (const auto& acc : accounts) { 3084 if (account(acc).name().compare(name) == 0) 3085 return true; 3086 } 3087 return false; 3088 } 3089 3090 QList<MyMoneyReport> MyMoneyFile::reportList() const 3091 { 3092 d->checkStorage(); 3093 3094 return d->m_storage->reportList(); 3095 } 3096 3097 void MyMoneyFile::addReport(MyMoneyReport& report) 3098 { 3099 d->checkTransaction(Q_FUNC_INFO); 3100 3101 d->m_storage->addReport(report); 3102 } 3103 3104 void MyMoneyFile::modifyReport(const MyMoneyReport& report) 3105 { 3106 d->checkTransaction(Q_FUNC_INFO); 3107 3108 d->m_storage->modifyReport(report); 3109 } 3110 3111 unsigned MyMoneyFile::countReports() const 3112 { 3113 d->checkStorage(); 3114 3115 return d->m_storage->countReports(); 3116 } 3117 3118 MyMoneyReport MyMoneyFile::report(const QString& id) const 3119 { 3120 d->checkStorage(); 3121 3122 return d->m_storage->report(id); 3123 } 3124 3125 void MyMoneyFile::removeReport(const MyMoneyReport& report) 3126 { 3127 d->checkTransaction(Q_FUNC_INFO); 3128 3129 d->m_storage->removeReport(report); 3130 } 3131 3132 3133 QList<MyMoneyBudget> MyMoneyFile::budgetList() const 3134 { 3135 d->checkStorage(); 3136 3137 return d->m_storage->budgetList(); 3138 } 3139 3140 void MyMoneyFile::addBudget(MyMoneyBudget &budget) 3141 { 3142 d->checkTransaction(Q_FUNC_INFO); 3143 3144 d->m_storage->addBudget(budget); 3145 } 3146 3147 MyMoneyBudget MyMoneyFile::budgetByName(const QString& name) const 3148 { 3149 d->checkStorage(); 3150 3151 return d->m_storage->budgetByName(name); 3152 } 3153 3154 void MyMoneyFile::modifyBudget(const MyMoneyBudget& budget) 3155 { 3156 d->checkTransaction(Q_FUNC_INFO); 3157 3158 d->m_storage->modifyBudget(budget); 3159 } 3160 3161 unsigned MyMoneyFile::countBudgets() const 3162 { 3163 d->checkStorage(); 3164 3165 return d->m_storage->countBudgets(); 3166 } 3167 3168 MyMoneyBudget MyMoneyFile::budget(const QString& id) const 3169 { 3170 d->checkStorage(); 3171 3172 return d->m_storage->budget(id); 3173 } 3174 3175 void MyMoneyFile::removeBudget(const MyMoneyBudget& budget) 3176 { 3177 d->checkTransaction(Q_FUNC_INFO); 3178 3179 d->m_storage->removeBudget(budget); 3180 } 3181 3182 void MyMoneyFile::addOnlineJob(onlineJob& job) 3183 { 3184 d->checkTransaction(Q_FUNC_INFO); 3185 3186 d->m_storage->addOnlineJob(job); 3187 d->m_changeSet += MyMoneyNotification(File::Mode::Add, job); 3188 } 3189 3190 void MyMoneyFile::modifyOnlineJob(const onlineJob job) 3191 { 3192 d->checkTransaction(Q_FUNC_INFO); 3193 d->m_storage->modifyOnlineJob(job); 3194 d->m_changeSet += MyMoneyNotification(File::Mode::Modify, job); 3195 } 3196 3197 onlineJob MyMoneyFile::getOnlineJob(const QString &jobId) const 3198 { 3199 d->checkStorage(); 3200 return d->m_storage->getOnlineJob(jobId); 3201 } 3202 3203 QList<onlineJob> MyMoneyFile::onlineJobList() const 3204 { 3205 d->checkStorage(); 3206 return d->m_storage->onlineJobList(); 3207 } 3208 3209 /** @todo improve speed by passing count job to m_storage */ 3210 int MyMoneyFile::countOnlineJobs() const 3211 { 3212 return onlineJobList().count(); 3213 } 3214 3215 /** 3216 * @brief Remove onlineJob 3217 * @param job onlineJob to remove 3218 */ 3219 void MyMoneyFile::removeOnlineJob(const onlineJob& job) 3220 { 3221 d->checkTransaction(Q_FUNC_INFO); 3222 3223 // clear all changed objects from cache 3224 if (job.isLocked()) { 3225 return; 3226 } 3227 d->m_changeSet += MyMoneyNotification(File::Mode::Remove, job); 3228 d->m_storage->removeOnlineJob(job); 3229 } 3230 3231 void MyMoneyFile::removeOnlineJob(const QStringList onlineJobIds) 3232 { 3233 foreach (QString jobId, onlineJobIds) { 3234 removeOnlineJob(getOnlineJob(jobId)); 3235 } 3236 } 3237 3238 void MyMoneyFile::costCenterList(QList< MyMoneyCostCenter >& list) const 3239 { 3240 d->checkStorage(); 3241 list = d->m_storage->costCenterList(); 3242 } 3243 3244 void MyMoneyFile::updateVAT(MyMoneyTransaction& transaction) const 3245 { 3246 // check if transaction qualifies 3247 const auto splitCount = transaction.splits().count(); 3248 if (splitCount > 1 && splitCount <= 3) { 3249 MyMoneyMoney amount; 3250 MyMoneyAccount assetLiability; 3251 MyMoneyAccount category; 3252 MyMoneySplit taxSplit; 3253 const QString currencyId = transaction.commodity(); 3254 foreach (const auto& split, transaction.splits()) { 3255 const auto acc = account(split.accountId()); 3256 // all splits must reference accounts denoted in the same currency 3257 if (acc.currencyId() != currencyId) { 3258 return; 3259 } 3260 if (acc.isAssetLiability() && assetLiability.id().isEmpty()) { 3261 amount = split.shares(); 3262 assetLiability = acc; 3263 continue; 3264 } 3265 if (acc.isAssetLiability()) { 3266 return; 3267 } 3268 if (category.id().isEmpty() && !acc.value("VatAccount").isEmpty()) { 3269 category = acc; 3270 continue; 3271 } else if(taxSplit.id().isEmpty() && !acc.value("Tax").toLower().compare(QLatin1String("yes"))) { 3272 taxSplit = split; 3273 continue; 3274 } 3275 return; 3276 } 3277 if (!category.id().isEmpty()) { 3278 // remove a possibly found tax split - we create a new one 3279 // but only if it is the same tax category 3280 if (!taxSplit.id().isEmpty()) { 3281 if (category.value("VatAccount").compare(taxSplit.accountId())) 3282 return; 3283 transaction.removeSplit(taxSplit); 3284 } 3285 addVATSplit(transaction, assetLiability, category, amount); 3286 } 3287 } 3288 } 3289 3290 bool MyMoneyFile::addVATSplit(MyMoneyTransaction& transaction, const MyMoneyAccount& acc, const MyMoneyAccount& category, const MyMoneyMoney& amount) const 3291 { 3292 bool rc = false; 3293 3294 try { 3295 MyMoneySplit cat; // category 3296 MyMoneySplit tax; // tax 3297 3298 if (category.value("VatAccount").isEmpty()) 3299 return false; 3300 MyMoneyAccount vatAcc = account(category.value("VatAccount")); 3301 const MyMoneySecurity& asec = security(acc.currencyId()); 3302 const MyMoneySecurity& csec = security(category.currencyId()); 3303 const MyMoneySecurity& vsec = security(vatAcc.currencyId()); 3304 if (asec.id() != csec.id() || asec.id() != vsec.id()) { 3305 qDebug("Auto VAT assignment only works if all three accounts use the same currency."); 3306 return false; 3307 } 3308 3309 MyMoneyMoney vatRate(vatAcc.value("VatRate")); 3310 MyMoneyMoney gv, nv; // gross value, net value 3311 int fract = acc.fraction(); 3312 3313 if (!vatRate.isZero()) { 3314 3315 tax.setAccountId(vatAcc.id()); 3316 3317 // qDebug("vat amount is '%s'", category.value("VatAmount").toLatin1()); 3318 if (category.value("VatAmount").toLower() != QString("net")) { 3319 // split value is the gross value 3320 gv = amount; 3321 nv = (gv / (MyMoneyMoney::ONE + vatRate)).convert(fract); 3322 MyMoneySplit catSplit = transaction.splitByAccount(acc.id(), false); 3323 catSplit.setShares(-nv); 3324 catSplit.setValue(catSplit.shares()); 3325 transaction.modifySplit(catSplit); 3326 3327 } else { 3328 // split value is the net value 3329 nv = amount; 3330 gv = (nv * (MyMoneyMoney::ONE + vatRate)).convert(fract); 3331 MyMoneySplit accSplit = transaction.splitByAccount(acc.id()); 3332 accSplit.setValue(gv.convert(fract)); 3333 accSplit.setShares(accSplit.value()); 3334 transaction.modifySplit(accSplit); 3335 } 3336 3337 tax.setValue(-(gv - nv).convert(fract)); 3338 tax.setShares(tax.value()); 3339 transaction.addSplit(tax); 3340 rc = true; 3341 } 3342 } catch (const MyMoneyException &) { 3343 } 3344 return rc; 3345 } 3346 3347 bool MyMoneyFile::isReferenced(const MyMoneyObject& obj, const QBitArray& skipChecks) const 3348 { 3349 d->checkStorage(); 3350 return d->m_storage->isReferenced(obj, skipChecks); 3351 } 3352 3353 bool MyMoneyFile::isReferenced(const MyMoneyObject& obj) const 3354 { 3355 return isReferenced(obj, QBitArray((int)eStorage::Reference::Count)); 3356 } 3357 3358 bool MyMoneyFile::checkNoUsed(const QString& accId, const QString& no) const 3359 { 3360 // by definition, an empty string or a non-numeric string is not used 3361 QRegExp exp(QString("(.*\\D)?(\\d+)(\\D.*)?")); 3362 if (no.isEmpty() || exp.indexIn(no) == -1) 3363 return false; 3364 3365 MyMoneyTransactionFilter filter; 3366 filter.addAccount(accId); 3367 QList<MyMoneyTransaction> transactions = transactionList(filter); 3368 QList<MyMoneyTransaction>::ConstIterator it_t = transactions.constBegin(); 3369 while (it_t != transactions.constEnd()) { 3370 try { 3371 MyMoneySplit split; 3372 // Test whether the transaction also includes a split into 3373 // this account 3374 split = (*it_t).splitByAccount(accId, true /*match*/); 3375 if (!split.number().isEmpty() && split.number() == no) 3376 return true; 3377 } catch (const MyMoneyException &) { 3378 } 3379 ++it_t; 3380 } 3381 return false; 3382 } 3383 3384 QString MyMoneyFile::highestCheckNo(const QString& accId) const 3385 { 3386 unsigned64 lno = 0; 3387 unsigned64 cno; 3388 QString no; 3389 MyMoneyTransactionFilter filter; 3390 filter.addAccount(accId); 3391 QList<MyMoneyTransaction> transactions = transactionList(filter); 3392 QList<MyMoneyTransaction>::ConstIterator it_t = transactions.constBegin(); 3393 while (it_t != transactions.constEnd()) { 3394 try { 3395 // Test whether the transaction also includes a split into 3396 // this account 3397 MyMoneySplit split = (*it_t).splitByAccount(accId, true /*match*/); 3398 if (!split.number().isEmpty()) { 3399 // non-numerical values stored in number will return 0 in the next line 3400 cno = split.number().toULongLong(); 3401 if (cno > lno) { 3402 lno = cno; 3403 no = split.number(); 3404 } 3405 } 3406 } catch (const MyMoneyException &) { 3407 } 3408 ++it_t; 3409 } 3410 return no; 3411 } 3412 3413 bool MyMoneyFile::hasNewerTransaction(const QString& accId, const QDate& date) const 3414 { 3415 MyMoneyTransactionFilter filter; 3416 filter.addAccount(accId); 3417 filter.setDateFilter(date.addDays(+1), QDate()); 3418 return !transactionList(filter).isEmpty(); 3419 } 3420 3421 void MyMoneyFile::clearCache() 3422 { 3423 d->checkStorage(); 3424 d->m_balanceCache.clear(); 3425 } 3426 3427 void MyMoneyFile::forceDataChanged() 3428 { 3429 emit dataChanged(); 3430 } 3431 3432 bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const 3433 { 3434 auto rc = true; 3435 if (t.splitCount() == 2) { 3436 foreach (const auto split, t.splits()) { 3437 auto acc = account(split.accountId()); 3438 if (acc.isIncomeExpense()) { 3439 rc = false; 3440 break; 3441 } 3442 } 3443 } 3444 return rc; 3445 } 3446 3447 bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const 3448 { 3449 auto ret = false; 3450 foreach (const auto split, t.splits()) { 3451 if (referencesClosedAccount(split)) { 3452 ret = true; 3453 break; 3454 } 3455 } 3456 return ret; 3457 } 3458 3459 bool MyMoneyFile::referencesClosedAccount(const MyMoneySplit& s) const 3460 { 3461 if (s.accountId().isEmpty()) 3462 return false; 3463 3464 try { 3465 return account(s.accountId()).isClosed(); 3466 } catch (const MyMoneyException &) { 3467 } 3468 return false; 3469 } 3470 3471 QString MyMoneyFile::storageId() 3472 { 3473 QString id = value("kmm-id"); 3474 if (id.isEmpty()) { 3475 MyMoneyFileTransaction ft; 3476 try { 3477 QUuid uid = QUuid::createUuid(); 3478 setValue("kmm-id", uid.toString()); 3479 ft.commit(); 3480 id = uid.toString(); 3481 } catch (const MyMoneyException &) { 3482 qDebug("Unable to setup UID for new storage object"); 3483 } 3484 } 3485 return id; 3486 } 3487 3488 QString MyMoneyFile::openingBalancesPrefix() 3489 { 3490 return i18n("Opening Balances"); 3491 } 3492 3493 bool MyMoneyFile::hasMatchingOnlineBalance(const MyMoneyAccount& _acc) const 3494 { 3495 // get current values 3496 auto acc = account(_acc.id()); 3497 3498 // if there's no last transaction import data we are done 3499 if (acc.value("lastImportedTransactionDate").isEmpty() 3500 || acc.value("lastStatementBalance").isEmpty()) 3501 return false; 3502 3503 // otherwise, we compare the balances 3504 MyMoneyMoney balance(acc.value("lastStatementBalance")); 3505 MyMoneyMoney accBalance = this->balance(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate)); 3506 3507 return balance == accBalance; 3508 } 3509 3510 int MyMoneyFile::countTransactionsWithSpecificReconciliationState(const QString& accId, TransactionFilter::State state) const 3511 { 3512 MyMoneyTransactionFilter filter; 3513 filter.addAccount(accId); 3514 filter.addState((int)state); 3515 return transactionList(filter).count(); 3516 } 3517 3518 QMap<QString, QVector<int> > MyMoneyFile::countTransactionsWithSpecificReconciliationState() const 3519 { 3520 QMap<QString, QVector<int> > result; 3521 MyMoneyTransactionFilter filter; 3522 filter.setReportAllSplits(false); 3523 3524 d->checkStorage(); 3525 3526 QList<MyMoneyAccount> list; 3527 accountList(list); 3528 for (const auto& account : list) { 3529 result[account.id()] = QVector<int>((int)eMyMoney::Split::State::MaxReconcileState, 0); 3530 } 3531 3532 const auto transactions = d->m_storage->transactionList(filter); 3533 for (const auto& transaction : transactions) { 3534 const auto& splits = transaction.splits(); 3535 for (const auto& split : splits) { 3536 if (!result.contains(split.accountId())) { 3537 result[split.accountId()] = QVector<int>((int)eMyMoney::Split::State::MaxReconcileState, 0); 3538 } 3539 const auto flag = split.reconcileFlag(); 3540 switch(flag) { 3541 case eMyMoney::Split::State::NotReconciled: 3542 case eMyMoney::Split::State::Cleared: 3543 case eMyMoney::Split::State::Reconciled: 3544 case eMyMoney::Split::State::Frozen: 3545 result[split.accountId()][(int)flag]++; 3546 break; 3547 default: 3548 break; 3549 } 3550 3551 } 3552 } 3553 return result; 3554 } 3555 3556 /** 3557 * Make sure that the splits value has the precision of the corresponding account 3558 */ 3559 void MyMoneyFile::fixSplitPrecision(MyMoneyTransaction& t) const 3560 { 3561 auto transactionSecurity = security(t.commodity()); 3562 auto transactionFraction = transactionSecurity.smallestAccountFraction(); 3563 3564 for (auto& split : t.splits()) { 3565 auto acc = account(split.accountId()); 3566 auto fraction = acc.fraction(); 3567 if(fraction == -1) { 3568 auto sec = security(acc.currencyId()); 3569 fraction = acc.fraction(sec); 3570 } 3571 // Don't do any rounding on a split factor 3572 if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) { 3573 split.setShares(static_cast<const MyMoneyMoney>(split.shares().convertDenominator(fraction).canonicalize())); 3574 split.setValue(static_cast<const MyMoneyMoney>(split.value().convertDenominator(transactionFraction).canonicalize())); 3575 } 3576 } 3577 } 3578 3579 MyMoneyMoney MyMoneyFile::stockSplit(const QString& accountId, MyMoneyMoney balance, MyMoneyMoney factor, eMyMoney::StockSplitDirection direction) const 3580 { 3581 d->checkStorage(); 3582 return d->m_storage->stockSplit(accountId, balance, factor, direction); 3583 } 3584 3585 class MyMoneyFileTransactionPrivate 3586 { 3587 Q_DISABLE_COPY(MyMoneyFileTransactionPrivate) 3588 3589 public: 3590 MyMoneyFileTransactionPrivate() : 3591 m_isNested(MyMoneyFile::instance()->hasTransaction()), 3592 m_needRollback(!m_isNested) 3593 { 3594 } 3595 3596 public: 3597 bool m_isNested; 3598 bool m_needRollback; 3599 3600 }; 3601 3602 3603 MyMoneyFileTransaction::MyMoneyFileTransaction() : 3604 d_ptr(new MyMoneyFileTransactionPrivate) 3605 { 3606 Q_D(MyMoneyFileTransaction); 3607 if (!d->m_isNested) 3608 MyMoneyFile::instance()->startTransaction(); 3609 } 3610 3611 MyMoneyFileTransaction::~MyMoneyFileTransaction() 3612 { 3613 try { 3614 rollback(); 3615 } catch (const MyMoneyException &e) { 3616 qDebug() << e.what(); 3617 } 3618 Q_D(MyMoneyFileTransaction); 3619 delete d; 3620 } 3621 3622 void MyMoneyFileTransaction::restart() 3623 { 3624 rollback(); 3625 3626 Q_D(MyMoneyFileTransaction); 3627 d->m_needRollback = !d->m_isNested; 3628 if (!d->m_isNested) 3629 MyMoneyFile::instance()->startTransaction(); 3630 } 3631 3632 void MyMoneyFileTransaction::commit() 3633 { 3634 Q_D(MyMoneyFileTransaction); 3635 if (!d->m_isNested) 3636 MyMoneyFile::instance()->commitTransaction(); 3637 d->m_needRollback = false; 3638 } 3639 3640 void MyMoneyFileTransaction::rollback() 3641 { 3642 Q_D(MyMoneyFileTransaction); 3643 if (d->m_needRollback) 3644 MyMoneyFile::instance()->rollbackTransaction(); 3645 d->m_needRollback = false; 3646 }