diff --git a/kmymoney/converter/tests/converter-test.cpp b/kmymoney/converter/tests/converter-test.cpp index 843ae771f..38826f9b5 100644 --- a/kmymoney/converter/tests/converter-test.cpp +++ b/kmymoney/converter/tests/converter-test.cpp @@ -1,201 +1,201 @@ /*************************************************************************** convertertest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "converter-test.h" #include #include // uses helper functions from reports tests #include "reportstestcommon.h" using namespace test; #include "mymoneyinstitution.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneyreport.h" #include "mymoneypayee.h" #include "mymoneystatement.h" #include "mymoneyexception.h" #include "storage/mymoneystoragexml.h" #include "storage/mymoneystoragedump.h" #include "webpricequote.h" QTEST_GUILESS_MAIN(ConverterTest) using namespace convertertest; void ConverterTest::init() { - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Thomas Baumgart"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount("Checking Account", eMyMoney::Account::Type::Checkings, moConverterCheckingOpen, QDate(2004, 5, 15), acAsset); acCredit = makeAccount("Credit Card", eMyMoney::Account::Type::CreditCard, moConverterCreditOpen, QDate(2004, 7, 15), acLiability); acSolo = makeAccount("Solo", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acParent = makeAccount("Parent", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acChild = makeAccount("Child", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acForeign = makeAccount("Foreign", eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void ConverterTest::cleanup() { file->detachStorage(storage); delete storage; } void ConverterTest::testWebQuotes_data() { QTest::addColumn("symbol"); QTest::addColumn("testname"); QTest::addColumn("source"); QTest::newRow("Yahoo UK") << "VOD.L" << "test Yahoo UK" << "Yahoo UK"; QTest::newRow("Yahoo Currency") << "EUR > USD" << "test Yahoo Currency" << "Yahoo Currency"; QTest::newRow("Financial Express") << "0585239" << "test Financial Express" << "Financial Express"; QTest::newRow("Yahoo France") << "EAD.PA" << "test Yahoo France" << "Yahoo France"; QTest::newRow("Globe & Mail") << "50492" << "test Globe-Mail" << "Globe & Mail"; QTest::newRow("MSN Canada") << "TDB647" << "test MSN.CA" << "MSN.CA"; // QTest::newRow("Finanztreff") << "BASF.SE" << "test Finanztreff" << "Finanztreff"; // QTest::newRow("boerseonline") << "symbol" << "test boerseonline" << "boerseonline"; // QTest::newRow("Wallstreet-Online.DE (Default)") << "symbol" << "test Wallstreet-Online.DE (Default)" << "Wallstreet-Online.DE (Default)"; // QTest::newRow("Financial Times UK") << "DZGEAE" << "test Financial Times UK Funds" << "Financial Times UK Funds"); QTest::newRow("Yahoo Canada") << "UTS.TO" << "test Yahoo Canada" << "Yahoo Canada"; // QTest::newRow("Wallstreed-Online.DE (Hamburg)") << "TDB647" << "test Wallstreet-Online.DE (Hamburg)" << "Wallstreet-Online.DE (Hamburg)"; // QTest::newRow("Gielda Papierow Wartosciowych (GPW)") << "TDB647" << "test Gielda Papierow Wartosciowych (GPW)" << "Gielda Papierow Wartosciowych (GPW)"; // QTest::newRow("OMX Baltic") << "TDB647" << "test OMX Baltic funds" << "OMX Baltic funds"; QTest::newRow("Finance::Quote usa") << "DIS" << "test F::Q usa" << "Finance::Quote usa"; //UNTESTED: Other F::Q sources, local files, user custom sources } void ConverterTest::testWebQuotesDefault() { #ifdef PERFORM_ONLINE_TESTS try { WebPriceQuote q; QuoteReceiver qr(&q); q.launch("DIS", "test default"); // qDebug() << "ConverterTest::testWebQuotes(): quote for " << q.m_symbol << " on " << qr.m_date.toString() << " is " << qr.m_price.toString() << " errors(" << qr.m_errors.count() << "): " << qr.m_errors.join(" /// "); // No errors allowed QVERIFY(qr.m_errors.count() == 0); // Quote date should be within the last week, or something bad is going on. QVERIFY(qr.m_date <= QDate::currentDate()); QVERIFY(qr.m_date >= QDate::currentDate().addDays(-7)); // Quote value should at least be positive QVERIFY(qr.m_price.isPositive()); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } #endif } void ConverterTest::testWebQuotes() { #ifdef PERFORM_ONLINE_TESTS try { WebPriceQuote q; QuoteReceiver qr(&q); QFETCH(QString, symbol); QFETCH(QString, testname); QFETCH(QString, source); q.launch(symbol, testname, source); QVERIFY(qr.m_errors.count() == 0); QVERIFY(qr.m_date <= QDate::currentDate().addDays(1)); QVERIFY(qr.m_date >= QDate::currentDate().addDays(-7)); QVERIFY(qr.m_price.isPositive()); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } #endif } void ConverterTest::testDateFormat() { try { MyMoneyDateFormat format("%mm-%dd-%yyyy"); QVERIFY(format.convertString("1-5-2005") == QDate(2005, 1, 5)); QVERIFY(format.convertString("jan-15-2005") == QDate(2005, 1, 15)); QVERIFY(format.convertString("august-25-2005") == QDate(2005, 8, 25)); format = MyMoneyDateFormat("%mm/%dd/%yy"); QVERIFY(format.convertString("1/5/05") == QDate(2005, 1, 5)); QVERIFY(format.convertString("jan/15/05") == QDate(2005, 1, 15)); QVERIFY(format.convertString("august/25/05") == QDate(2005, 8, 25)); format = MyMoneyDateFormat("%d\\.%m\\.%yy"); QVERIFY(format.convertString("1.5.05") == QDate(2005, 5, 1)); QVERIFY(format.convertString("15.jan.05") == QDate(2005, 1, 15)); QVERIFY(format.convertString("25.august.05") == QDate(2005, 8, 25)); format = MyMoneyDateFormat("%yyyy\\\\%dddd\\\\%mmmmmmmmmmm"); QVERIFY(format.convertString("2005\\31\\12") == QDate(2005, 12, 31)); QVERIFY(format.convertString("2005\\15\\jan") == QDate(2005, 1, 15)); QVERIFY(format.convertString("2005\\25\\august") == QDate(2005, 8, 25)); format = MyMoneyDateFormat("%m %dd, %yyyy"); QVERIFY(format.convertString("jan 15, 2005") == QDate(2005, 1, 15)); QVERIFY(format.convertString("august 25, 2005") == QDate(2005, 8, 25)); QVERIFY(format.convertString("january 1st, 2005") == QDate(2005, 1, 1)); format = MyMoneyDateFormat("%m %d %y"); QVERIFY(format.convertString("12/31/50", false, 2000) == QDate(1950, 12, 31)); QVERIFY(format.convertString("1/1/90", false, 2000) == QDate(1990, 1, 1)); QVERIFY(format.convertString("december 31st, 5", false) == QDate(2005, 12, 31)); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } diff --git a/kmymoney/converter/tests/converter-test.h b/kmymoney/converter/tests/converter-test.h index 7a7ef9909..14b39d841 100644 --- a/kmymoney/converter/tests/converter-test.h +++ b/kmymoney/converter/tests/converter-test.h @@ -1,43 +1,43 @@ /*************************************************************************** convertertest.h ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef CONVERTERTEST_H #define CONVERTERTEST_H #include #include "mymoneyfile.h" -#include "storage/mymoneyseqaccessmgr.h" +#include "storage/mymoneystoragemgr.h" class ConverterTest : public QObject { Q_OBJECT private: - MyMoneySeqAccessMgr* storage; + MyMoneyStorageMgr* storage; MyMoneyFile* file; private Q_SLOTS: void init(); void cleanup(); void testWebQuotesDefault(); void testWebQuotes_data(); void testWebQuotes(); void testDateFormat(); }; #endif // CONVERTERTEST_H diff --git a/kmymoney/converter/tests/matchfinder-test.cpp b/kmymoney/converter/tests/matchfinder-test.cpp index ae6400afe..38b29fdec 100644 --- a/kmymoney/converter/tests/matchfinder-test.cpp +++ b/kmymoney/converter/tests/matchfinder-test.cpp @@ -1,484 +1,484 @@ /*************************************************************************** KMyMoney transaction importing module - tests for ExistingTransactionMatchFinder copyright : (C) 2012 by Lukasz Maszczynski ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "matchfinder-test.h" #include "existingtransactionmatchfinder.h" #include #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneymoney.h" #include "mymoneyenums.h" QTEST_GUILESS_MAIN(MatchFinderTest) void MatchFinderTest::init() { file = MyMoneyFile::instance(); setupStorage(); setupCurrency(); setupAccounts(); setupPayees(); ledgerTransaction = buildDefaultTransaction(); importTransaction = buildDefaultTransaction(); existingTrFinder.reset(new ExistingTransactionMatchFinder(MATCH_WINDOW)); schedule = buildNonOverdueSchedule(); scheduledTrFinder.reset(new ScheduledTransactionMatchFinder(*account, MATCH_WINDOW)); } void MatchFinderTest::cleanup() { file->detachStorage(storage.data()); } void MatchFinderTest::setupStorage() { - storage.reset(new MyMoneySeqAccessMgr); + storage.reset(new MyMoneyStorageMgr); file->attachStorage(storage.data()); } void MatchFinderTest::setupCurrency() { MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->setBaseCurrency(file->currency("USD")); ft.commit(); } void MatchFinderTest::setupPayees() { payee.setName("John Doe"); otherPayee.setName("Jane Doe"); MyMoneyFileTransaction ft; file->addPayee(payee); file->addPayee(otherPayee); ft.commit(); } void MatchFinderTest::setupAccounts() { account.reset(new MyMoneyAccount); otherAccount.reset(new MyMoneyAccount); account->setName("Expenses account"); account->setAccountType(eMyMoney::Account::Type::Expense); account->setOpeningDate(QDate(2012, 12, 01)); account->setCurrencyId(MyMoneyFile::instance()->baseCurrency().id()); otherAccount->setName("Some other account"); otherAccount->setAccountType(eMyMoney::Account::Type::Expense); otherAccount->setOpeningDate(QDate(2012, 12, 01)); otherAccount->setCurrencyId(MyMoneyFile::instance()->baseCurrency().id()); MyMoneyFileTransaction ft; MyMoneyAccount parentAccount = file->expense(); file->addAccount(*account, parentAccount); file->addAccount(*otherAccount, parentAccount); ft.commit(); } MyMoneyTransaction MatchFinderTest::buildDefaultTransaction() const { MyMoneySplit split; split.setAccountId(account->id()); split.setBankID("#1"); split.setPayeeId(payee.id()); split.setShares(MyMoneyMoney(123.00)); MyMoneyTransaction transaction; transaction.setPostDate(QDate(2012, 12, 5)); transaction.setImported(true); transaction.addSplit(split); return transaction; } MyMoneyTransaction MatchFinderTest::buildMatchedTransaction(MyMoneyTransaction transaction) const { transaction.clearId(); MyMoneyTransaction matchingTransaction = transaction; transaction.splits().first().addMatch(matchingTransaction); return transaction; } QString MatchFinderTest::addTransactionToLedger(MyMoneyTransaction transaction) const { MyMoneyFileTransaction ft; file->addTransaction(transaction); ft.commit(); return transaction.id(); } MyMoneySchedule MatchFinderTest::buildNonOverdueSchedule() const { QDate tomorrow = QDate::currentDate().addDays(1); MyMoneyTransaction transaction = buildDefaultTransaction(); transaction.setPostDate(tomorrow); MyMoneySchedule nonOverdueSchedule("schedule name", eMyMoney::Schedule::Type::Transfer, eMyMoney::Schedule::Occurrence::Monthly, 1, eMyMoney::Schedule::PaymentType::BankTransfer, tomorrow, tomorrow.addMonths(2), false, false); nonOverdueSchedule.setTransaction(transaction); return nonOverdueSchedule; } void MatchFinderTest::addSchedule(MyMoneySchedule schedule) const { MyMoneyFileTransaction ft; file->addSchedule(schedule); ft.commit(); } void MatchFinderTest::expectMatchWithExistingTransaction(TransactionMatchFinder::MatchResult expectedResult) { matchResult = existingTrFinder->findMatch(importTransaction, importTransaction.splits().first()); QCOMPARE(matchResult, expectedResult); } void MatchFinderTest::expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchResult expectedResult) { matchResult = scheduledTrFinder->findMatch(importTransaction, importTransaction.splits().first()); QCOMPARE(matchResult, expectedResult); } void MatchFinderTest::testDuplicate_allMatch() { addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchDuplicate); } void MatchFinderTest::testDuplicate_payeeEmpty() { ledgerTransaction.splits().first().setPayeeId(payee.id()); importTransaction.splits().first().setPayeeId(""); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchDuplicate); } void MatchFinderTest::testDuplicate_payeeMismatch() { ledgerTransaction.splits().first().setPayeeId(payee.id()); importTransaction.splits().first().setPayeeId(otherPayee.id()); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchDuplicate); } void MatchFinderTest::testDuplicate_splitIsMarkedAsMatched() { ledgerTransaction = buildMatchedTransaction(ledgerTransaction); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchDuplicate); } void MatchFinderTest::testPreciseMatch_noBankId() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchPrecise); } void MatchFinderTest::testPreciseMatch_importBankId() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID("#1"); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchPrecise); } void MatchFinderTest::testPreciseMatch_payeeEmpty() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); importTransaction.splits().first().setPayeeId(""); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchPrecise); } void MatchFinderTest::testImpreciseMatch_matchWindowLowerBound() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); importTransaction.setPostDate(importTransaction.postDate().addDays(-MATCH_WINDOW)); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchImprecise); } void MatchFinderTest::testImpreciseMatch_matchWindowUpperBound() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); importTransaction.setPostDate(importTransaction.postDate().addDays(MATCH_WINDOW)); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchImprecise); } void MatchFinderTest::testImpreciseMatch_payeeEmpty() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); importTransaction.splits().first().setPayeeId(""); importTransaction.setPostDate(importTransaction.postDate().addDays(1)); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchImprecise); } void MatchFinderTest::testNoMatch_bankIdMismatch() { ledgerTransaction.splits().first().setBankID("#1"); importTransaction.splits().first().setBankID("#2"); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_ledgerBankIdNotEmpty() { ledgerTransaction.splits().first().setBankID("#1"); importTransaction.splits().first().setBankID(""); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_accountMismatch_withBankId() { ledgerTransaction.splits().first().setAccountId(account->id()); importTransaction.splits().first().setAccountId(otherAccount->id()); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_accountMismatch_noBankId() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); ledgerTransaction.splits().first().setAccountId(account->id()); importTransaction.splits().first().setAccountId(otherAccount->id()); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_amountMismatch_withBankId() { ledgerTransaction.splits().first().setShares(MyMoneyMoney(1.11)); importTransaction.splits().first().setShares(MyMoneyMoney(9.99)); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_amountMismatch_noBankId() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); ledgerTransaction.splits().first().setShares(MyMoneyMoney(1.11)); importTransaction.splits().first().setShares(MyMoneyMoney(9.99)); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_payeeMismatch() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); ledgerTransaction.splits().first().setPayeeId(payee.id()); importTransaction.splits().first().setPayeeId(otherPayee.id()); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_splitIsMarkedAsMatched() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); ledgerTransaction = buildMatchedTransaction(ledgerTransaction); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_postDateMismatch_withBankId() { importTransaction.setPostDate(importTransaction.postDate().addDays(MATCH_WINDOW + 1)); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testNoMatch_postDateMismatch_noBankId() { ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setBankID(""); importTransaction.setPostDate(importTransaction.postDate().addDays(MATCH_WINDOW + 1)); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testExistingTransactionMatch_sameTransactionId_withBankId() { QString transactionId = addTransactionToLedger(ledgerTransaction); importTransaction = MyMoneyTransaction(transactionId, ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testExistingTransactionMatch_sameTransactionId_noBankId() { ledgerTransaction.splits().first().setBankID(""); QString transactionId = addTransactionToLedger(ledgerTransaction); importTransaction = MyMoneyTransaction(transactionId, ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testExistingTransactionMatch_multipleAccounts_withBankId() { ledgerTransaction.splits().first().setAccountId(account->id()); ledgerTransaction.splits().first().setBankID("#1"); importTransaction.splits().first().setAccountId(otherAccount->id()); importTransaction.splits().first().setBankID("#1"); MyMoneySplit secondSplit = importTransaction.splits().first(); secondSplit.clearId(); secondSplit.setAccountId(account->id()); secondSplit.setBankID("#1"); importTransaction.addSplit(secondSplit); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testExistingTransactionMatch_multipleAccounts_noBankId() { ledgerTransaction.splits().first().setAccountId(account->id()); ledgerTransaction.splits().first().setBankID(""); importTransaction.splits().first().setAccountId(otherAccount->id()); importTransaction.splits().first().setBankID(""); MyMoneySplit secondSplit = importTransaction.splits().first(); secondSplit.clearId(); secondSplit.setAccountId(account->id()); secondSplit.setBankID(""); importTransaction.addSplit(secondSplit); addTransactionToLedger(ledgerTransaction); expectMatchWithExistingTransaction(TransactionMatchFinder::MatchNotFound); } void MatchFinderTest::testScheduleMatch_allMatch() { importTransaction.setPostDate(schedule.adjustedNextDueDate()); addSchedule(schedule); expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchPrecise); QCOMPARE(schedule.isOverdue(), false); } void MatchFinderTest::testScheduleMatch_dueDateWithinMatchWindow() { QDate dateWithinMatchWindow = schedule.adjustedNextDueDate().addDays(MATCH_WINDOW); importTransaction.setPostDate(dateWithinMatchWindow); addSchedule(schedule); expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchImprecise); QCOMPARE(schedule.isOverdue(), false); } void MatchFinderTest::testScheduleMatch_amountWithinAllowedVariation() { double exactAmount = schedule.transaction().splits()[0].shares().toDouble(); double amountWithinAllowedVariation = exactAmount * (100 + schedule.variation()) / 100; importTransaction.splits()[0].setShares(MyMoneyMoney(amountWithinAllowedVariation)); importTransaction.setPostDate(schedule.adjustedNextDueDate()); addSchedule(schedule); expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchPrecise); } void MatchFinderTest::testScheduleMatch_overdue() { schedule.setNextDueDate(QDate::currentDate().addDays(-MATCH_WINDOW - 1)); importTransaction.setPostDate(QDate::currentDate()); addSchedule(schedule); expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchImprecise); QCOMPARE(schedule.isOverdue(), true); } void MatchFinderTest::testScheduleMismatch_dueDate() { importTransaction.setPostDate(schedule.adjustedNextDueDate().addDays(MATCH_WINDOW + 1)); addSchedule(schedule); expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchNotFound); QCOMPARE(schedule.isOverdue(), false); } void MatchFinderTest::testScheduleMismatch_amount() { double exactAmount = schedule.transaction().splits()[0].shares().toDouble(); double mismatchedAmount = exactAmount * (110 + schedule.variation()) / 100; importTransaction.splits()[0].setShares(MyMoneyMoney(mismatchedAmount)); importTransaction.setPostDate(schedule.adjustedNextDueDate()); addSchedule(schedule); expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchNotFound); } diff --git a/kmymoney/converter/tests/matchfinder-test.h b/kmymoney/converter/tests/matchfinder-test.h index 0ecb2e090..5b00cc06a 100644 --- a/kmymoney/converter/tests/matchfinder-test.h +++ b/kmymoney/converter/tests/matchfinder-test.h @@ -1,108 +1,108 @@ /*************************************************************************** KMyMoney transaction importing module - tests for ExistingTransactionMatchFinder copyright : (C) 2012 by Lukasz Maszczynski ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MATCHFINDERTEST_H #define MATCHFINDERTEST_H #include #include #include "mymoneyaccount.h" #include "mymoneypayee.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" #include "existingtransactionmatchfinder.h" #include "scheduledtransactionmatchfinder.h" class MyMoneyFile; class MatchFinderTest : public QObject { Q_OBJECT private: MyMoneyFile * file; QScopedPointer account; QScopedPointer otherAccount; - QScopedPointer storage; + QScopedPointer storage; MyMoneyPayee payee; MyMoneyPayee otherPayee; static const int MATCH_WINDOW = 4; MyMoneyTransaction ledgerTransaction; MyMoneyTransaction importTransaction; TransactionMatchFinder::MatchResult matchResult; QScopedPointer existingTrFinder; MyMoneySchedule schedule; QScopedPointer scheduledTrFinder; void setupStorage(); void setupCurrency(); void setupAccounts(); void setupPayees(); MyMoneyTransaction buildDefaultTransaction() const; MyMoneyTransaction buildMatchedTransaction(MyMoneyTransaction transaction) const; QString addTransactionToLedger(MyMoneyTransaction transaction) const; MyMoneySchedule buildNonOverdueSchedule() const; void addSchedule(MyMoneySchedule schedule) const; void expectMatchWithExistingTransaction(TransactionMatchFinder::MatchResult expectedResult); void expectMatchWithScheduledTransaction(TransactionMatchFinder::MatchResult expectedResult); private Q_SLOTS: void init(); void cleanup(); void testDuplicate_allMatch(); void testDuplicate_payeeEmpty(); void testDuplicate_payeeMismatch(); void testDuplicate_splitIsMarkedAsMatched(); void testPreciseMatch_noBankId(); void testPreciseMatch_importBankId(); void testPreciseMatch_payeeEmpty(); void testImpreciseMatch_matchWindowLowerBound(); void testImpreciseMatch_matchWindowUpperBound(); void testImpreciseMatch_payeeEmpty(); void testNoMatch_bankIdMismatch(); void testNoMatch_ledgerBankIdNotEmpty(); void testNoMatch_accountMismatch_withBankId(); void testNoMatch_accountMismatch_noBankId(); void testNoMatch_amountMismatch_withBankId(); void testNoMatch_amountMismatch_noBankId(); void testNoMatch_payeeMismatch(); void testNoMatch_splitIsMarkedAsMatched(); void testNoMatch_postDateMismatch_withBankId(); void testNoMatch_postDateMismatch_noBankId(); void testExistingTransactionMatch_sameTransactionId_withBankId(); void testExistingTransactionMatch_sameTransactionId_noBankId(); void testExistingTransactionMatch_multipleAccounts_withBankId(); void testExistingTransactionMatch_multipleAccounts_noBankId(); void testScheduleMatch_allMatch(); void testScheduleMatch_dueDateWithinMatchWindow(); void testScheduleMatch_amountWithinAllowedVariation(); void testScheduleMatch_overdue(); void testScheduleMismatch_dueDate(); void testScheduleMismatch_amount(); }; #endif // MATCHFINDERTEST_H diff --git a/kmymoney/dialogs/kgeneratesqldlg.cpp b/kmymoney/dialogs/kgeneratesqldlg.cpp index fbe2a8439..7e9995513 100644 --- a/kmymoney/dialogs/kgeneratesqldlg.cpp +++ b/kmymoney/dialogs/kgeneratesqldlg.cpp @@ -1,341 +1,341 @@ /*************************************************************************** kgeneratesqldlg.cpp ------------------- copyright : (C) 2009 by Tony Bloomfield (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kgeneratesqldlg.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // System includes // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kgeneratesqldlg.h" #include "mymoneyexception.h" #include "mymoneyfile.h" -#include "storage/mymoneyseqaccessmgr.h" +#include "storage/mymoneystoragemgr.h" #include "kguiutils.h" #include "misc/platformtools.h" #include "mymoneydbdriver.h" #include "mymoneydbdef.h" class KGenerateSqlDlgPrivate { Q_DISABLE_COPY(KGenerateSqlDlgPrivate) Q_DECLARE_PUBLIC(KGenerateSqlDlg) public: explicit KGenerateSqlDlgPrivate(KGenerateSqlDlg *qq) : q_ptr(qq), ui(new Ui::KGenerateSqlDlg) { } ~KGenerateSqlDlgPrivate() { delete ui; } void init() { Q_Q(KGenerateSqlDlg); ui->setupUi(q); m_createTablesButton = ui->buttonBox->addButton(i18n("Create Tables"), QDialogButtonBox::ButtonRole::AcceptRole); m_saveSqlButton = ui->buttonBox->addButton(i18n("Save SQL"), QDialogButtonBox::ButtonRole::ActionRole); Q_ASSERT(m_createTablesButton); Q_ASSERT(m_saveSqlButton); q->connect(ui->buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); q->connect(ui->buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); initializeForm(); } void initializeForm() { Q_Q(KGenerateSqlDlg); m_requiredFields = nullptr; // at this point, we don't know which fields are required, so disable everything but the list m_saveSqlButton->setEnabled(false); m_createTablesButton->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->urlSqlite->clear(); ui->textDbName->clear(); ui->textHostName->clear(); ui->textPassword->clear(); ui->textUserName->clear(); ui->textSQL->clear(); ui->urlSqlite->setEnabled(false); ui->textDbName->setEnabled(false); ui->textHostName->setEnabled(false); ui->textPassword->setEnabled(false); ui->textUserName->setEnabled(false); ui->textSQL->setEnabled(false); q->connect(ui->buttonBox->button(QDialogButtonBox::Help), &QPushButton::clicked, q, &KGenerateSqlDlg::slotHelp); } QString selectedDriver() { auto drivers = ui->listDrivers->selectedItems(); if (drivers.count() != 1) { return QString(); } return drivers[0]->text().section(' ', 0, 0); } KGenerateSqlDlg *q_ptr; Ui::KGenerateSqlDlg *ui; QPushButton *m_createTablesButton; QPushButton *m_saveSqlButton; QList m_supportedDrivers; //MyMoneyDbDrivers m_map; std::unique_ptr m_requiredFields; bool m_sqliteSelected; QExplicitlySharedDataPointer m_dbDriver; QString m_dbName; - MyMoneySeqAccessMgr* m_storage; + MyMoneyStorageMgr* m_storage; bool m_mustDetachStorage; }; KGenerateSqlDlg::KGenerateSqlDlg(QWidget *parent) : QDialog(parent), d_ptr(new KGenerateSqlDlgPrivate(this)) { Q_D(KGenerateSqlDlg); d->init(); } KGenerateSqlDlg::~KGenerateSqlDlg() { Q_D(KGenerateSqlDlg); delete d; } int KGenerateSqlDlg::exec() { Q_D(KGenerateSqlDlg); // list drivers supported by KMM auto map = MyMoneyDbDriver::driverMap(); // list drivers installed on system auto list = QSqlDatabase::drivers(); // join the two QStringList::Iterator it = list.begin(); while (it != list.end()) { QString dname = *it; if (map.keys().contains(dname)) { // only keep if driver is supported dname = dname + " - " + map[dname]; d->m_supportedDrivers.append(dname); } ++it; } if (d->m_supportedDrivers.count() == 0) { // why does KMessageBox not have a standard dialog with Help button? if ((KMessageBox::questionYesNo(this, i18n("In order to use a database, you need to install some additional software. Click Help for more information"), i18n("No Qt SQL Drivers"), KStandardGuiItem::help(), KStandardGuiItem::cancel())) == KMessageBox::Yes) { // Yes stands in for help here KHelpClient::invokeHelp("details.database.usage"); } return (1); } d->ui->listDrivers->clear(); d->ui->listDrivers->addItems(d->m_supportedDrivers); connect(d->ui->listDrivers, &QListWidget::itemSelectionChanged, this, &KGenerateSqlDlg::slotdriverSelected); return (QDialog::exec()); } void KGenerateSqlDlg::slotcreateTables() { Q_D(KGenerateSqlDlg); if (d->m_sqliteSelected) { d->m_dbName = d->ui->urlSqlite->text(); } else { d->m_dbName = d->ui->textDbName->text(); } // check that the database has been pre-created { // all queries etc. must be in a block - see 'remove database' API doc Q_ASSERT(!d->selectedDriver().isEmpty()); QSqlDatabase dbase = QSqlDatabase::addDatabase(d->selectedDriver(), "creation"); dbase.setHostName(d->ui->textHostName->text()); dbase.setDatabaseName(d->m_dbName); dbase.setUserName(d->ui->textUserName->text()); dbase.setPassword(d->ui->textPassword->text()); if (!dbase.open()) { KMessageBox::error(this, i18n("Unable to open database.\n" "You must use an SQL CREATE DATABASE statement before creating the tables.\n") ); return; } QSqlQuery q(dbase); QString message(i18n("Tables successfully created")); QStringList commands = d->ui->textSQL->toPlainText().split('\n'); QStringList::ConstIterator cit; for (cit = commands.constBegin(); cit != commands.constEnd(); ++cit) { if (!(*cit).isEmpty()) { //qDebug() << "exec" << *cit; q.prepare(*cit); if (!q.exec()) { QSqlError e = q.lastError(); message = i18n("Creation failed executing statement" "\nExecuted: %1" "\nError No %2: %3", q.executedQuery(), e.number(), e.text()); break; } } } KMessageBox::information(this, message); } QSqlDatabase::removeDatabase("creation"); auto okButton = d->ui->buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(okButton); okButton->setEnabled(true); } void KGenerateSqlDlg::slotsaveSQL() { Q_D(KGenerateSqlDlg); auto fileName = QFileDialog::getSaveFileName( this, i18n("Select output file"), QString(), QString()); if (fileName.isEmpty()) return; QFile out(fileName); if (!out.open(QIODevice::WriteOnly)) return; QTextStream s(&out); MyMoneyDbDef db; s << d->ui->textSQL->toPlainText(); out.close(); auto okButton = d->ui->buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(okButton); okButton->setEnabled(true); } void KGenerateSqlDlg::slotdriverSelected() { Q_D(KGenerateSqlDlg); const auto driverName = d->selectedDriver(); if (driverName.isEmpty()) { d->initializeForm(); return; } d->m_dbDriver = MyMoneyDbDriver::create(driverName); if (!d->m_dbDriver->isTested()) { int rc = KMessageBox::warningContinueCancel(0, i18n("Database type %1 has not been fully tested in a KMyMoney environment.\n" "Please make sure you have adequate backups of your data.\n" "Please report any problems to the developer mailing list at " "kmymoney-devel@kde.org", driverName), ""); if (rc == KMessageBox::Cancel) { d->ui->listDrivers->clearSelection(); d->initializeForm(); return; } } d->m_requiredFields.reset(new KMandatoryFieldGroup(this)); // currently, only sqlite need an external file if (d->m_dbDriver->requiresExternalFile()) { d->m_sqliteSelected = true; d->ui->urlSqlite->setMode(KFile::Mode::File); d->ui->urlSqlite->setEnabled(true); d->m_requiredFields->add(d->ui->urlSqlite); d->ui->textDbName->setEnabled(false); d->ui->textHostName->setEnabled(false); d->ui->textUserName->setEnabled(false); } else { // not sqlite3 d->m_sqliteSelected = false; d->ui->urlSqlite->setEnabled(false); d->ui->textDbName->setEnabled(true); d->ui->textHostName->setEnabled(true); d->ui->textUserName->setEnabled(true); d->m_requiredFields->add(d->ui->textDbName); d->m_requiredFields->add(d->ui->textHostName); d->m_requiredFields->add(d->ui->textUserName); d->ui->textDbName->setText("KMyMoney"); d->ui->textHostName->setText("localhost"); d->ui->textUserName->setText(""); d->ui->textUserName->setText(platformTools::osUsername()); d->ui->textPassword->setText(""); } d->ui->textPassword->setEnabled(d->m_dbDriver->isPasswordSupported()); d->m_requiredFields->setOkButton(d->m_createTablesButton); d->ui->textSQL->setEnabled(true); // check if we have a storage; if not, create a skeleton one // we need a storage for MyMoneyDbDef to generate standard accounts - d->m_storage = new MyMoneySeqAccessMgr; + d->m_storage = new MyMoneyStorageMgr; d->m_mustDetachStorage = true; try { MyMoneyFile::instance()->attachStorage(d->m_storage); } catch (const MyMoneyException &) { d->m_mustDetachStorage = false; // there is already a storage attached } MyMoneyDbDef db; d->ui->textSQL->setText (db.generateSQL(d->m_dbDriver)); if (d->m_mustDetachStorage) { MyMoneyFile::instance()->detachStorage(); } delete d->m_storage; d->m_saveSqlButton->setEnabled(true); connect(d->m_saveSqlButton, &QPushButton::clicked, this, &KGenerateSqlDlg::slotsaveSQL); connect(d->m_createTablesButton, &QPushButton::clicked, this, &KGenerateSqlDlg::slotcreateTables); } void KGenerateSqlDlg::slotHelp() { KHelpClient::invokeHelp("details.database.generatesql"); } diff --git a/kmymoney/dialogs/kmymoneyfileinfodlg.cpp b/kmymoney/dialogs/kmymoneyfileinfodlg.cpp index 200dac462..f7accafb2 100644 --- a/kmymoney/dialogs/kmymoneyfileinfodlg.cpp +++ b/kmymoney/dialogs/kmymoneyfileinfodlg.cpp @@ -1,103 +1,103 @@ /*************************************************************************** kmymoneyfileinfodlg.cpp - description ------------------- begin : Sun Oct 9 2005 copyright : (C) 2005 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmymoneyfileinfodlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ui_kmymoneyfileinfodlg.h" -#include +#include "mymoneystoragemgr.h" #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneypayee.h" #include "mymoneyprice.h" #include "mymoneyschedule.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneyenums.h" KMyMoneyFileInfoDlg::KMyMoneyFileInfoDlg(QWidget *parent) : QDialog(parent), ui(new Ui::KMyMoneyFileInfoDlg) { ui->setupUi(this); // Now fill the fields with data auto storage = MyMoneyFile::instance()->storage(); ui->m_creationDate->setText(storage->creationDate().toString(Qt::ISODate)); ui->m_lastModificationDate->setText(storage->lastModificationDate().toString(Qt::ISODate)); ui->m_baseCurrency->setText(storage->value("kmm-baseCurrency")); ui->m_payeeCount->setText(QString::fromLatin1("%1").arg(storage->payeeList().count())); ui->m_institutionCount->setText(QString::fromLatin1("%1").arg(storage->institutionList().count())); QList a_list; storage->accountList(a_list); ui->m_accountCount->setText(QString::fromLatin1("%1").arg(a_list.count())); QMap accountMap; QMap accountMapClosed; QList::const_iterator it_a; for (it_a = a_list.constBegin(); it_a != a_list.constEnd(); ++it_a) { accountMap[(*it_a).accountType()] = accountMap[(*it_a).accountType()] + 1; accountMapClosed[(*it_a).accountType()] = accountMapClosed[(*it_a).accountType()] + 0; if ((*it_a).isClosed()) accountMapClosed[(*it_a).accountType()] = accountMapClosed[(*it_a).accountType()] + 1; } QMap::const_iterator it_m; for (it_m = accountMap.constBegin(); it_m != accountMap.constEnd(); ++it_m) { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, MyMoneyAccount::accountTypeToString(it_m.key())); item->setText(1, QString::fromLatin1("%1").arg(*it_m)); item->setText(2, QString::fromLatin1("%1").arg(accountMapClosed[it_m.key()])); ui->m_accountView->invisibleRootItem()->addChild(item); } MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); ui->m_transactionCount->setText(QString::fromLatin1("%1").arg(storage->transactionList(filter).count())); filter.setReportAllSplits(true); ui->m_splitCount->setText(QString::fromLatin1("%1").arg(storage->transactionList(filter).count())); ui->m_scheduleCount->setText(QString::fromLatin1("%1").arg(storage->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), false).count())); MyMoneyPriceList list = storage->priceList(); MyMoneyPriceList::const_iterator it_p; int pCount = 0; for (it_p = list.constBegin(); it_p != list.constEnd(); ++it_p) pCount += (*it_p).count(); ui->m_priceCount->setText(QString::fromLatin1("%1").arg(pCount)); } KMyMoneyFileInfoDlg::~KMyMoneyFileInfoDlg() { delete ui; } diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index d7e4bbc3e..8baad3d21 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,3412 +1,3407 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ****************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoney.h" // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyglobalsettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kenterscheduledlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/kfindtransactiondlg.h" #include "dialogs/knewbankdlg.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kselectdatabasedlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.h" #include "dialogs/kgeneratesqldlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/kgpgkeyselectiondlg.h" #include "dialogs/ktemplateexportdlg.h" #include "dialogs/transactionmatcher.h" #include "wizards/newuserwizard/knewuserwizard.h" #include "wizards/newaccountwizard/knewaccountwizard.h" #include "dialogs/kbalancewarning.h" #include "widgets/kmymoneyaccountselector.h" #include "widgets/kmymoneypayeecombo.h" #include "widgets/onlinejobmessagesview.h" #include "widgets/kmymoneymvccombo.h" #include "views/kmymoneyview.h" #include "views/konlinejoboutbox.h" #include "models/onlinejobmessagesmodel.h" #include "mymoney/mymoneyobject.h" #include "mymoney/mymoneyfile.h" #include "mymoney/mymoneyinstitution.h" #include "mymoney/mymoneyaccount.h" #include "mymoney/mymoneyaccountloan.h" #include "mymoney/mymoneysecurity.h" #include "mymoney/mymoneypayee.h" #include "mymoney/mymoneytag.h" #include "mymoney/mymoneybudget.h" #include "mymoney/mymoneyreport.h" #include "mymoney/mymoneysplit.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" -#include "mymoney/storage/mymoneystoragedump.h" -#include "mymoney/storage/imymoneystorage.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/mymoneytransactionfilter.h" #include "mymoney/onlinejobmessage.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "tasks/credittransfer.h" #include "icons/icons.h" #include "misc/webconnect.h" -#include "storage/imymoneyserialize.h" +#include "storage/mymoneystoragemgr.h" #include "storage/mymoneystoragesql.h" #include #include "transactioneditor.h" #include "konlinetransferform.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" #include "storageenums.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "menuenums.h" #include "misc/platformtools.h" // includes needed for shared global settings #include "mymoney_config.h" #include "widgets_config.h" #ifdef KMM_DEBUG +#include "mymoney/storage/mymoneystoragedump.h" #include "mymoneytracer.h" #endif using namespace Icons; using namespace eMenu; static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; // define the default period to warn about an expiring recoverkey to 30 days // but allows to override this setting during build time #ifndef RECOVER_KEY_EXPIRATION_WARNING #define RECOVER_KEY_EXPIRATION_WARNING 30 #endif QHash pActions; QHash pMenus; enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: Private(KMyMoneyApp *app) : q(app), m_ft(0), m_moveToAccountSelector(0), m_statementXMLindex(0), m_balanceWarning(0), m_collectingStatements(false), m_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(0), m_progressBar(0), m_smtReader(0), m_searchDlg(0), m_autoSaveTimer(0), m_progressTimer(0), m_inAutoSaving(false), m_transactionEditor(0), m_endingBalanceDlg(0), m_saveEncrypted(0), m_additionalKeyLabel(0), m_additionalKeyButton(0), m_recentFiles(0), #ifdef KF5Holidays_FOUND m_holidayRegion(0), #endif m_applicationIsReady(true), m_webConnect(new WebConnect(app)) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void closeFile(); void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); /** * The public interface. */ KMyMoneyApp * const q; MyMoneyFileTransaction* m_ft; KMyMoneyAccountSelector* m_moveToAccountSelector; int m_statementXMLindex; KBalanceWarning* m_balanceWarning; bool m_collectingStatements; QStringList m_statementResults; QString m_lastPayeeEnteredId; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * @brief Structure of plugins objects by their interfaces */ KMyMoneyPlugin::Container m_plugins; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; KProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; /// The URL of the file currently being edited when open. QUrl m_fileName; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; MyMoneyStatementReader* m_smtReader; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; KFindTransactionDlg* m_searchDlg; MyMoneyAccount m_selectedAccount; MyMoneyAccount m_reconciliationAccount; MyMoneySchedule m_selectedSchedule; KMyMoneyRegister::SelectedTransactions m_selectedTransactions; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // pointer to the current transaction editor TransactionEditor* m_transactionEditor; // Reconciliation dialog KEndingBalanceDlg* m_endingBalanceDlg; // Pointer to the combo box used for key selection during // File/Save as KComboBox* m_saveEncrypted; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; QStringList m_additionalGpgKeys; QLabel* m_additionalKeyLabel; QPushButton* m_additionalKeyButton; KRecentFilesAction* m_recentFiles; #ifdef KF5Holidays_FOUND // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; WebConnect* m_webConnect; // methods void consistencyCheck(bool alwaysDisplayResults); static void setThemedCSS(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { #ifdef KMM_DBUS new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney", QDBusConnectionInterface::DontQueueService); #endif // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); MyMoney::injectExternalSettings(KMyMoneyGlobalSettings::self()); Widgets::injectExternalSettings(KMyMoneyGlobalSettings::self()); // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); d->setThemedCSS(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); updateCaption(true); QFrame* frame = new QFrame; frame->setFrameStyle(QFrame::NoFrame); // values for margin (11) and spacing(6) taken from KDialog implementation QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(6); { QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants if (!themeName.isEmpty() && themeName != QLatin1Literal("system")) // if it isn't default theme then set it QIcon::setThemeName(themeName); Icons::setIconThemeNames(QIcon::themeName()); // get whatever theme user ends up with and hope our icon names will fit that theme } initStatusBar(); pActions = initActions(); pMenus = initMenus(); d->m_myMoneyView = new KMyMoneyView(this/*the global variable kmymoney is not yet assigned. So we pass it here*/); layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, &KMyMoneyView::aboutToChangeView, this, &KMyMoneyApp::slotResetSelections); connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotUpdateActions())); connect(d->m_myMoneyView, &KMyMoneyView::statusMsg, this, &KMyMoneyApp::slotStatusMsg); connect(d->m_myMoneyView, &KMyMoneyView::statusProgress, this, &KMyMoneyApp::slotStatusProgressBar); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Load, d->m_plugins, this, guiFactory()); onlineJobAdministration::instance()->setOnlinePlugins(d->m_plugins.extended); d->m_myMoneyView->setOnlinePlugins(d->m_plugins.online); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents())); // force to show the home page if the file is closed connect(pActions[Action::ViewTransactionDetail], &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail); d->m_backupState = BACKUP_IDLE; QLocale locale; int weekStart = locale.firstDayOfWeek(); int weekEnd = weekStart-1; if (weekEnd < Qt::Monday) { weekEnd = Qt::Sunday; } bool startFirst = (weekStart < weekEnd); for (int i = 0; i < 8; ++i) { if (startFirst) d->m_processingDays.setBit(i, (i >= weekStart && i <= weekEnd)); else d->m_processingDays.setBit(i, (i >= weekStart || i <= weekEnd)); } d->m_autoSaveTimer = new QTimer(this); d->m_progressTimer = new QTimer(this); connect(d->m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); connect(d->m_progressTimer, SIGNAL(timeout()), this, SLOT(slotStatusProgressDone())); // make sure, we get a note when the engine changes state connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotDataChanged())); // connect the WebConnect server connect(d->m_webConnect, SIGNAL(gotUrl(QUrl)), this, SLOT(webConnect(QUrl))); // make sure we have a balance warning object d->m_balanceWarning = new KBalanceWarning(this); // setup the initial configuration slotUpdateConfiguration(QString()); // kickstart date change timer slotDateChanged(); connect(this, SIGNAL(fileLoaded(QUrl)), onlineJobAdministration::instance(), SLOT(updateOnlineTaskProperties())); } KMyMoneyApp::~KMyMoneyApp() { // delete cached objects since the are in the way // when unloading the plugins onlineJobAdministration::instance()->clearCaches(); // we need to unload all plugins before we destroy anything else KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Unload, d->m_plugins, this, guiFactory()); delete d->m_searchDlg; delete d->m_transactionEditor; delete d->m_endingBalanceDlg; delete d->m_moveToAccountSelector; #ifdef KF5Holidays_FOUND delete d->m_holidayRegion; #endif Widgets::injectExternalSettings(nullptr); MyMoney::injectExternalSettings(nullptr); delete d; } QUrl KMyMoneyApp::lastOpenedURL() { QUrl url = d->m_startDialog ? QUrl() : d->m_fileName; if (!url.isValid()) { url = QUrl::fromUserInput(readLastUsedFile()); } ready(); return url; } void KMyMoneyApp::slotObjectDestroyed(QObject* o) { if (o == d->m_moveToAccountSelector) { d->m_moveToAccountSelector = 0; } } void KMyMoneyApp::slotInstallConsistencyCheckContextMenu() { // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list, // please adjust it if it's necessary or rewrite the way the consistency check results are displayed if (QWidget* dialog = QApplication::activeModalWidget()) { if (QListWidget* widget = dialog->findChild()) { // give the user a hint that the data can be saved widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it.")); widget->setWhatsThis(widget->toolTip()); widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint))); } } } void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos) { // allow the user to save the consistency check results if (QWidget* widget = qobject_cast< QWidget* >(sender())) { QMenu contextMenu(widget); QAction* copy = new QAction(i18n("Copy to clipboard"), widget); QAction* save = new QAction(i18n("Save to file"), widget); contextMenu.addAction(copy); contextMenu.addAction(save); QAction *result = contextMenu.exec(widget->mapToGlobal(pos)); if (result == copy) { // copy the consistency check results to the clipboard d->copyConsistencyCheckResults(); } else if (result == save) { // save the consistency check results to a file d->saveConsistencyCheckResults(); } } } QHash KMyMoneyApp::initMenus() { QHash lutMenus; const QHash menuNames { {Menu::Institution, QStringLiteral("institution_context_menu")}, {Menu::Account, QStringLiteral("account_context_menu")}, {Menu::Schedule, QStringLiteral("schedule_context_menu")}, {Menu::Category, QStringLiteral("category_context_menu")}, {Menu::Tag, QStringLiteral("tag_context_menu")}, {Menu::Payee, QStringLiteral("payee_context_menu")}, {Menu::Investment, QStringLiteral("investment_context_menu")}, {Menu::Transaction, QStringLiteral("transaction_context_menu")}, {Menu::MoveTransaction, QStringLiteral("transaction_move_menu")}, {Menu::MarkTransaction, QStringLiteral("transaction_mark_menu")}, {Menu::MarkTransactionContext, QStringLiteral("transaction_context_mark_menu")}, {Menu::OnlineJob, QStringLiteral("onlinejob_context_menu")} }; for (auto it = menuNames.cbegin(); it != menuNames.cend(); ++it) lutMenus.insert(it.key(), qobject_cast(factory()->container(it.value(), this))); return lutMenus; } QHash KMyMoneyApp::initActions() { auto aC = actionCollection(); // ************* // Adding standard actions // ************* KStandardAction::openNew(this, &KMyMoneyApp::slotFileNew, aC); KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC); d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC); KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC); KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC); KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC); KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC); KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC); /* Look-up table for all custom actions. It's required for: 1) building QList with QActions to be added to ActionCollection 2) adding custom features to QActions like e.g. keyboard shortcut */ QHash lutActions; // ************* // Adding all actions // ************* { // struct for creating useless (unconnected) QAction struct actionInfo { Action action; QString name; QString text; Icon icon; }; const QVector actionInfos { // ************* // The File menu // ************* {Action::FileOpenDatabase, QStringLiteral("open_database"), i18n("Open database..."), Icon::SVNUpdate}, {Action::FileSaveAsDatabase, QStringLiteral("saveas_database"), i18n("Save as database..."), Icon::FileArchiver}, {Action::FileBackup, QStringLiteral("file_backup"), i18n("Backup..."), Icon::Empty}, {Action::FileImportStatement, QStringLiteral("file_import_statement"), i18n("Statement file..."), Icon::Empty}, {Action::FileImportTemplate, QStringLiteral("file_import_template"), i18n("Account Template..."), Icon::Empty}, {Action::FileExportTemplate, QStringLiteral("file_export_template"), i18n("Account Template..."), Icon::Empty}, {Action::FilePersonalData, QStringLiteral("view_personal_data"), i18n("Personal Data..."), Icon::UserProperties}, #ifdef KMM_DEBUG {Action::FileDump, QStringLiteral("file_dump"), i18n("Dump Memory"), Icon::Empty}, #endif {Action::FileInformation, QStringLiteral("view_file_info"), i18n("File-Information..."), Icon::DocumentProperties}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, QStringLiteral("edit_find_transaction"), i18n("Find transaction..."), Icon::EditFindTransaction}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail"), i18n("Show Transaction Detail"), Icon::ViewTransactionDetail}, {Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions"), i18n("Hide reconciled transactions"), Icon::HideReconciled}, {Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories"), i18n("Hide unused categories"), Icon::HideCategories}, {Action::ViewShowAll, QStringLiteral("view_show_all_accounts"), i18n("Show all accounts"), Icon::Empty}, // ********************* // The institutions menu // ********************* {Action::NewInstitution, QStringLiteral("institution_new"), i18n("New institution..."), Icon::InstitutionNew}, {Action::EditInstitution, QStringLiteral("institution_edit"), i18n("Edit institution..."), Icon::InstitutionEdit}, {Action::DeleteInstitution, QStringLiteral("institution_delete"), i18n("Delete institution..."), Icon::InstitutionDelete}, // ***************** // The accounts menu // ***************** {Action::NewAccount, QStringLiteral("account_new"), i18n("New account..."), Icon::AccountNew}, {Action::OpenAccount, QStringLiteral("account_open"), i18n("Open ledger"), Icon::ViewFinancialList}, {Action::StartReconciliation, QStringLiteral("account_reconcile"), i18n("Reconcile..."), Icon::Reconcile}, {Action::FinishReconciliation, QStringLiteral("account_reconcile_finish"), i18nc("Finish reconciliation", "Finish"), Icon::AccountFinishReconciliation}, {Action::PostponeReconciliation, QStringLiteral("account_reconcile_postpone"), i18n("Postpone reconciliation"), Icon::MediaPlaybackPause}, {Action::EditAccount, QStringLiteral("account_edit"), i18n("Edit account..."), Icon::AccountEdit}, {Action::DeleteAccount, QStringLiteral("account_delete"), i18n("Delete account..."), Icon::AccountDelete}, {Action::CloseAccount, QStringLiteral("account_close"), i18n("Close account"), Icon::AccountClose}, {Action::ReopenAccount, QStringLiteral("account_reopen"), i18n("Reopen account"), Icon::AccountReopen}, {Action::ReportAccountTransactions, QStringLiteral("account_transaction_report"), i18n("Transaction report"), Icon::ViewFinancialList}, {Action::ChartAccountBalance, QStringLiteral("account_chart"), i18n("Show balance chart..."), Icon::OfficeChartLine}, {Action::MapOnlineAccount, QStringLiteral("account_online_map"), i18n("Map account..."), Icon::NewsSubscribe}, {Action::UnmapOnlineAccount, QStringLiteral("account_online_unmap"), i18n("Unmap account..."), Icon::NewsUnsubscribe}, {Action::UpdateAccount, QStringLiteral("account_online_update"), i18n("Update account..."), Icon::AccountUpdate}, {Action::UpdateAllAccounts, QStringLiteral("account_online_update_all"), i18n("Update all accounts..."), Icon::AccountUpdateAll}, {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"), Icon::AccountCreditTransfer}, // ******************* // The categories menu // ******************* {Action::NewCategory, QStringLiteral("category_new"), i18n("New category..."), Icon::CategoryNew}, {Action::EditCategory, QStringLiteral("category_edit"), i18n("Edit category..."), Icon::CategoryEdit}, {Action::DeleteCategory, QStringLiteral("category_delete"), i18n("Delete category..."), Icon::CategoryDelete}, // ************** // The tools menu // ************** {Action::ToolCurrencies, QStringLiteral("tools_currency_editor"), i18n("Currencies..."), Icon::ViewCurrencyList}, {Action::ToolPrices, QStringLiteral("tools_price_editor"), i18n("Prices..."), Icon::Empty}, {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices"), i18n("Update Stock and Currency Prices..."), Icon::ToolUpdatePrices}, {Action::ToolConsistency, QStringLiteral("tools_consistency_check"), i18n("Consistency Check"), Icon::Empty}, {Action::ToolPerformance, QStringLiteral("tools_performancetest"), i18n("Performance-Test"), Icon::Fork}, {Action::ToolSQL, QStringLiteral("tools_generate_sql"), i18n("Generate Database SQL"), Icon::Empty}, {Action::ToolCalculator, QStringLiteral("tools_kcalc"), i18n("Calculator..."), Icon::AccessoriesCalculator}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, QStringLiteral("settings_enable_messages"), i18n("Enable all messages"), Icon::Empty}, // ************* // The help menu // ************* {Action::HelpShow, QStringLiteral("help_show_tip"), i18n("&Show tip of the day"), Icon::Tip}, // *************************** // Actions w/o main menu entry // *************************** {Action::NewTransaction, QStringLiteral("transaction_new"), i18nc("New transaction button", "New"), Icon::TransactionNew}, {Action::EditTransaction, QStringLiteral("transaction_edit"), i18nc("Edit transaction button", "Edit"), Icon::TransactionEdit}, {Action::EnterTransaction, QStringLiteral("transaction_enter"), i18nc("Enter transaction", "Enter"), Icon::DialogOK}, {Action::EditSplits, QStringLiteral("transaction_editsplits"), i18nc("Edit split button", "Edit splits"), Icon::Split}, {Action::CancelTransaction, QStringLiteral("transaction_cancel"), i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel}, {Action::DeleteTransaction, QStringLiteral("transaction_delete"), i18nc("Delete transaction", "Delete"), Icon::EditDelete}, {Action::DuplicateTransaction, QStringLiteral("transaction_duplicate"), i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy}, {Action::MatchTransaction, QStringLiteral("transaction_match"), i18nc("Button text for match transaction", "Match"),Icon::TransactionMatch}, {Action::AcceptTransaction, QStringLiteral("transaction_accept"), i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::TransactionAccept}, {Action::ToggleReconciliationFlag, QStringLiteral("transaction_mark_toggle"), i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty}, {Action::MarkCleared, QStringLiteral("transaction_mark_cleared"), i18nc("Mark transaction cleared", "Cleared"), Icon::Empty}, {Action::MarkReconciled, QStringLiteral("transaction_mark_reconciled"), i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty}, {Action::MarkNotReconciled, QStringLiteral("transaction_mark_notreconciled"), i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty}, {Action::SelectAllTransactions, QStringLiteral("transaction_select_all"), i18nc("Select all transactions", "Select all"), Icon::Empty}, {Action::GoToAccount, QStringLiteral("transaction_goto_account"), i18n("Go to account"), Icon::GoJump}, {Action::GoToPayee, QStringLiteral("transaction_goto_payee"), i18n("Go to payee"), Icon::GoJump}, {Action::NewScheduledTransaction, QStringLiteral("transaction_create_schedule"), i18n("Create scheduled transaction..."), Icon::AppointmentNew}, {Action::AssignTransactionsNumber, QStringLiteral("transaction_assign_number"), i18n("Assign next number"), Icon::Empty}, {Action::CombineTransactions, QStringLiteral("transaction_combine"), i18nc("Combine transactions", "Combine"), Icon::Empty}, {Action::CopySplits, QStringLiteral("transaction_copy_splits"), i18n("Copy splits"), Icon::Empty}, //Investment {Action::NewInvestment, QStringLiteral("investment_new"), i18n("New investment..."), Icon::InvestmentNew}, {Action::EditInvestment, QStringLiteral("investment_edit"), i18n("Edit investment..."), Icon::InvestmentEdit}, {Action::DeleteInvestment, QStringLiteral("investment_delete"), i18n("Delete investment..."), Icon::InvestmentDelete}, {Action::UpdatePriceOnline, QStringLiteral("investment_online_price_update"), i18n("Online price update..."), Icon::InvestmentOnlinePrice}, {Action::UpdatePriceManually, QStringLiteral("investment_manual_price_update"), i18n("Manual price update..."), Icon::Empty}, //Schedule {Action::NewSchedule, QStringLiteral("schedule_new"), i18n("New scheduled transaction"), Icon::AppointmentNew}, {Action::EditSchedule, QStringLiteral("schedule_edit"), i18n("Edit scheduled transaction"), Icon::DocumentEdit}, {Action::DeleteSchedule, QStringLiteral("schedule_delete"), i18n("Delete scheduled transaction"), Icon::EditDelete}, {Action::DuplicateSchedule, QStringLiteral("schedule_duplicate"), i18n("Duplicate scheduled transaction"), Icon::EditCopy}, {Action::EnterSchedule, QStringLiteral("schedule_enter"), i18n("Enter next transaction..."), Icon::KeyEnter}, {Action::SkipSchedule, QStringLiteral("schedule_skip"), i18n("Skip next transaction..."), Icon::MediaSeekForward}, //Payees {Action::NewPayee, QStringLiteral("payee_new"), i18n("New payee"), Icon::ListAddUser}, {Action::RenamePayee, QStringLiteral("payee_rename"), i18n("Rename payee"), Icon::PayeeRename}, {Action::DeletePayee, QStringLiteral("payee_delete"), i18n("Delete payee"), Icon::ListRemoveUser}, {Action::MergePayee, QStringLiteral("payee_merge"), i18n("Merge payees"), Icon::PayeeMerge}, //Tags {Action::NewTag, QStringLiteral("tag_new"), i18n("New tag"), Icon::ListAddTag}, {Action::RenameTag, QStringLiteral("tag_rename"), i18n("Rename tag"), Icon::TagRename}, {Action::DeleteTag, QStringLiteral("tag_delete"), i18n("Delete tag"), Icon::ListRemoveTag}, //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, QStringLiteral("new_user_wizard"), i18n("Test new feature"), Icon::Empty}, {Action::DebugTraces, QStringLiteral("debug_traces"), i18n("Debug Traces"), Icon::Empty}, #endif {Action::DebugTimers, QStringLiteral("debug_timers"), i18n("Debug Timers"), Icon::Empty}, // onlineJob actions {Action::DeleteOnlineJob, QStringLiteral("onlinejob_delete"), i18n("Remove credit transfer"), Icon::EditDelete}, {Action::EditOnlineJob, QStringLiteral("onlinejob_edit"), i18n("Edit credit transfer"), Icon::DocumentEdit}, {Action::LogOnlineJob, QStringLiteral("onlinejob_log"), i18n("Show log"), Icon::Empty}, }; for (const auto& info : actionInfos) { auto a = new QAction(this); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(info.name); a->setText(info.text); if (info.icon != Icon::Empty) // no need to set empty icon a->setIcon(Icons::get(info.icon)); a->setEnabled(false); lutActions.insert(info.action, a); // store QAction's pointer for later processing } } { // List with slots that get connected here. Other slots get connected in e.g. appropriate views typedef void(KMyMoneyApp::*KMyMoneyAppFunc)(); const QHash actionConnections { // ************* // The File menu // ************* {Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase}, {Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase}, {Action::FileBackup, &KMyMoneyApp::slotBackupFile}, {Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates}, {Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates}, {Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal}, #ifdef KMM_DEBUG {Action::FileDump, &KMyMoneyApp::slotFileFileInfo}, #endif {Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, &KMyMoneyApp::slotFindTransaction}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail}, {Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions}, {Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories}, {Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts}, // ***************** // The accounts menu // ***************** {Action::MapOnlineAccount, &KMyMoneyApp::slotAccountMapOnline}, {Action::UnmapOnlineAccount, &KMyMoneyApp::slotAccountUnmapOnline}, {Action::UpdateAccount, &KMyMoneyApp::slotAccountUpdateOnline}, {Action::UpdateAllAccounts, &KMyMoneyApp::slotAccountUpdateOnlineAll}, // ************** // The tools menu // ************** {Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog}, {Action::ToolPrices, &KMyMoneyApp::slotPriceDialog}, {Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate}, {Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck}, {Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest}, {Action::ToolSQL, &KMyMoneyApp::slotGenerateSql}, {Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages}, // ************* // The help menu // ************* {Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay}, // *************************** // Actions w/o main menu entry // *************************** //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, &KMyMoneyApp::slotNewFeature}, {Action::DebugTraces, &KMyMoneyApp::slotToggleTraces}, #endif {Action::DebugTimers, &KMyMoneyApp::slotToggleTimers}, }; for (auto connection = actionConnections.cbegin(); connection != actionConnections.cend(); ++connection) connect(lutActions[connection.key()], &QAction::triggered, this, connection.value()); } // ************* // Setting some of added actions checkable // ************* { // Some of acitions schould be checkable, // so set them here const QVector checkableActions { Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories, #ifdef KMM_DEBUG Action::DebugTraces, #endif Action::ViewShowAll }; for (const auto& it : checkableActions) { lutActions[it]->setCheckable(true); lutActions[it]->setEnabled(true); } } // ************* // Setting keyboard shortcuts for some of added actions // ************* { const QVector> actionShortcuts { {qMakePair(Action::EditFindTransaction, Qt::CTRL + Qt::Key_F)}, {qMakePair(Action::ViewTransactionDetail, Qt::CTRL + Qt::Key_T)}, {qMakePair(Action::ViewHideReconciled, Qt::CTRL + Qt::Key_R)}, {qMakePair(Action::ViewHideCategories, Qt::CTRL + Qt::Key_U)}, {qMakePair(Action::ViewShowAll, Qt::CTRL + Qt::SHIFT + Qt::Key_A)}, {qMakePair(Action::StartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)}, {qMakePair(Action::NewTransaction, Qt::CTRL + Qt::Key_Insert)}, {qMakePair(Action::ToggleReconciliationFlag, Qt::CTRL + Qt::Key_Space)}, {qMakePair(Action::MarkCleared, Qt::CTRL + Qt::Key_Alt + Qt::Key_Space)}, {qMakePair(Action::MarkReconciled, Qt::CTRL + Qt::Key_Shift + Qt::Key_Space)}, {qMakePair(Action::SelectAllTransactions, Qt::CTRL + Qt::Key_A)}, #ifdef KMM_DEBUG {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif {qMakePair(Action::AssignTransactionsNumber, Qt::CTRL + Qt::Key_Shift + Qt::Key_N)} }; for(const auto& it : actionShortcuts) aC->setDefaultShortcut(lutActions[it.first], it.second); } // ************* // Misc settings // ************* connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged, lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled); // Setup transaction detail switch lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneyGlobalSettings::showRegisterDetailed()); lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); lutActions[Action::ViewHideCategories]->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); lutActions[Action::ViewShowAll]->setChecked(false); // ************* // Adding actions to ActionCollection // ************* actionCollection()->addActions(lutActions.values()); // ************************ // Currently unused actions // ************************ #if 0 new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back"); new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward"); action("go_back")->setEnabled(false); action("go_forward")->setEnabled(false); #endif // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); // reconnect about app entry to dialog with full credits information auto aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp))); aboutApp->disconnect(); connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits); QMenu *menuContainer; menuContainer = static_cast(factory()->container(QStringLiteral("import"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentImport)); menuContainer = static_cast(factory()->container(QStringLiteral("export"), this)); menuContainer->setIcon(Icons::get(Icon::DocumentExport)); return lutActions; } #ifdef KMM_DEBUG void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); foreach (const auto it, list) std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl; } #endif bool KMyMoneyApp::isActionToggled(const Action _a) { return pActions[_a]->isChecked(); } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel; statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar; statusBar()->addWidget(d->m_progressBar); d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8); // hide the progress bar for now slotStatusProgressBar(-1, -1); } void KMyMoneyApp::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); grp.writeEntry("Show Statusbar", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->isChecked()); KConfigGroup toolbarGrp = d->m_config->group("mainToolBar"); toolBar("mainToolBar")->saveSettings(toolbarGrp); d->m_recentFiles->saveEntries(d->m_config->group("Recent Files")); } void KMyMoneyApp::readOptions() { KConfigGroup grp = d->m_config->group("General Options"); pActions[Action::ViewHideReconciled]->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); pActions[Action::ViewHideCategories]->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); d->m_recentFiles->loadEntries(d->m_config->group("Recent Files")); // Startdialog is written in the settings dialog d->m_startDialog = grp.readEntry("StartDialog", true); } void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); updateCaption(true); } int KMyMoneyApp::askSaveOnClose() { int ans; if (KMyMoneyGlobalSettings::autoSaveOnClose()) { ans = KMessageBox::Yes; } else { ans = KMessageBox::warningYesNoCancel(this, i18n("The file has been changed, save it?")); } return ans; } bool KMyMoneyApp::queryClose() { if (!isReady()) return false; if (d->m_myMoneyView->dirty()) { int ans = askSaveOnClose(); if (ans == KMessageBox::Cancel) return false; else if (ans == KMessageBox::Yes) { bool saved = slotFileSave(); saveOptions(); return saved; } } - if (d->m_myMoneyView->isDatabase()) - slotFileClose(); // close off the database +// if (d->m_myMoneyView->isDatabase()) +// slotFileClose(); // close off the database saveOptions(); return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KMyMoneyApp::slotFileInfoDialog() { QPointer dlg = new KMyMoneyFileInfoDlg(0); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPerformanceTest() { // dump performance report to stderr int measurement[2]; QTime timer; MyMoneyAccount acc; qDebug("--- Starting performance tests ---"); // AccountList MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; timer.start(); for (int i = 0; i < 1000; ++i) { QList list; MyMoneyFile::instance()->accountList(list); measurement[i != 0] = timer.elapsed(); } std::cerr << "accountList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of asset account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of asset account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "totalBalance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of expense account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of expense account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); timer.start(); for (int i = 0; i < 1000; ++i) { MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] = timer.elapsed(); } std::cerr << "totalBalance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { list = MyMoneyFile::instance()->transactionList(filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { MyMoneyFile::instance()->transactionList(list, filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList(list)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } MyMoneyFile::instance()->preloadCache(); } void KMyMoneyApp::slotFileNew() { KMSTATUS(i18n("Creating new document...")); slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->newFile(); d->m_fileName = QUrl(); updateCaption(); NewUserWizard::Wizard *wizard = new NewUserWizard::Wizard(); if (wizard->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { // store the user info file->setUser(wizard->user()); // create and setup base currency file->addCurrency(wizard->baseCurrency()); file->setBaseCurrency(wizard->baseCurrency()); // create a possible institution MyMoneyInstitution inst = wizard->institution(); if (inst.name().length()) { file->addInstitution(inst); } // create a possible checking account auto acc = wizard->account(); if (acc.name().length()) { acc.setInstitutionId(inst.id()); MyMoneyAccount asset = file->asset(); file->addAccount(acc, asset); // create possible opening balance transaction if (!wizard->openingBalance().isZero()) { file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); } } // import the account templates QList templates = wizard->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } d->m_fileName = wizard->url(); ft.commit(); KMyMoneyGlobalSettings::setFirstTimeRun(false); // FIXME This is a bit clumsy. We re-read the freshly // created file to be able to run through all the // fixup logic and then save it to keep the modified // flag off. slotFileSave(); d->m_myMoneyView->readFile(d->m_fileName); slotFileSave(); // now keep the filename in the recent files used list //KRecentFilesAction *p = dynamic_cast(action(KStandardAction::name(KStandardAction::OpenRecent))); //if(p) d->m_recentFiles->addUrl(d->m_fileName); writeLastUsedFile(d->m_fileName.url()); } catch (const MyMoneyException &) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } if (wizard->startSettingsAfterFinished()) slotSettings(); } else { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } delete wizard; updateCaption(); emit fileLoaded(d->m_fileName); } } // General open void KMyMoneyApp::slotFileOpen() { KMSTATUS(i18n("Open a file.")); QString prevDir = readLastUsedDir(); QPointer dialog = new QFileDialog(this, QString(), prevDir, i18n("KMyMoney files (*.kmy *.xml);;All files")); dialog->setFileMode(QFileDialog::ExistingFile); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { slotFileOpenRecent(dialog->selectedUrls().first()); } delete dialog; } void KMyMoneyApp::slotOpenDatabase() { KMSTATUS(i18n("Open a file.")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite); if (!dialog->checkDrivers()) { delete dialog; return; } if (dialog->exec() == QDialog::Accepted && dialog != 0) { slotFileOpenRecent(dialog->selectedURL()); } delete dialog; } bool KMyMoneyApp::isImportableFile(const QUrl &url) { bool result = false; // Iterate through the plugins and see if there's a loaded plugin who can handle it QMap::const_iterator it_plugin = d->m_plugins.importer.constBegin(); while (it_plugin != d->m_plugins.importer.constEnd()) { if ((*it_plugin)->isMyFormat(url.path())) { result = true; break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_plugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url.path())) result = true; // Place code here to test for QIF and other locally-supported formats // (i.e. not a plugin). If you add them here, be sure to add it to // the webConnect function. return result; } void KMyMoneyApp::slotFileOpenRecent(const QUrl &url) { KMSTATUS(i18n("Loading file...")); QUrl lastFile = d->m_fileName; // check if there are other instances which might have this file open QList list = instanceList(); QList::ConstIterator it; bool duplicate = false; #ifdef KMM_DBUS for (it = list.constBegin(); duplicate == false && it != list.constEnd(); ++it) { QDBusInterface remoteApp(*it, "/KMymoney", "org.kde.kmymoney"); QDBusReply reply = remoteApp.call("filename"); if (!reply.isValid()) { qDebug("D-Bus error while calling app->filename()"); } else { if (reply.value() == url.url()) { duplicate = true; } } } #endif if (!duplicate) { QUrl newurl = url; if ((newurl.scheme() == QLatin1String("sql"))) { const QString key = QLatin1String("driver"); // take care and convert some old url to their new counterpart QUrlQuery query(newurl); if (query.queryItemValue(key) == QLatin1String("QMYSQL3")) { // fix any old urls query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QMYSQL")); } if (query.queryItemValue(key) == QLatin1String("QSQLITE3")) { query.removeQueryItem(key); query.addQueryItem(key, QLatin1String("QSQLITE")); } newurl.setQuery(query); if (query.queryItemValue(key) == QLatin1String("QSQLITE")) { newurl.setUserInfo(QString()); newurl.setHost(QString()); } // check if a password is needed. it may be if the URL came from the last/recent file list QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, newurl); if (!dialog->checkDrivers()) { delete dialog; return; } // if we need to supply a password, then show the dialog // otherwise it isn't needed if ((query.queryItemValue("secure").toLower() == QLatin1String("yes")) && newurl.password().isEmpty()) { if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { newurl = dialog->selectedURL(); } else { delete dialog; return; } } delete dialog; } if (newurl.scheme() == QLatin1String("sql") || KMyMoneyUtils::fileExists(newurl)) { slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { try { if (d->m_myMoneyView->readFile(newurl)) { if ((d->m_myMoneyView->isNativeFile())) { d->m_fileName = newurl; updateCaption(); d->m_recentFiles->addUrl(newurl); writeLastUsedFile(newurl.toDisplayString(QUrl::PreferLocalFile)); } else { d->m_fileName = QUrl(); // imported files have no filename } // Check the schedules slotCheckSchedules(); } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", e.what())); } updateCaption(); emit fileLoaded(d->m_fileName); } else { /*fileOpen failed - should we do something or maybe fileOpen puts out the message... - it does for database*/ } } else { // newurl invalid slotFileClose(); KMessageBox::sorry(this, i18n("

%1 is either an invalid filename or the file does not exist. You can open another file or create a new one.

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); } } else { // isDuplicate KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open")); } } bool KMyMoneyApp::slotFileSave() { // if there's nothing changed, there's no need to save anything if (!d->m_myMoneyView->dirty()) return true; bool rc = false; KMSTATUS(i18n("Saving file...")); if (d->m_fileName.isEmpty()) return slotFileSaveAs(); d->consistencyCheck(false); - /*if (myMoneyView->isDatabase()) { - rc = myMoneyView->saveDatabase(m_fileName); - // the 'save' function is no longer relevant for a database*/ setEnabled(false); - rc = d->m_myMoneyView->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); + if (d->m_myMoneyView->isDatabase()) + rc = d->m_myMoneyView->saveDatabase(d->m_fileName); + else + rc = d->m_myMoneyView->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); setEnabled(true); d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileSaveAsFilterChanged(const QString& filter) { if (!d->m_saveEncrypted) return; if (filter.compare(QLatin1String("*.kmy"), Qt::CaseInsensitive) != 0) { d->m_saveEncrypted->setCurrentItem(0); d->m_saveEncrypted->setEnabled(false); } else { d->m_saveEncrypted->setEnabled(true); } } void KMyMoneyApp::slotManageGpgKeys() { QPointer dlg = new KGpgKeySelectionDlg(this); dlg->setKeys(d->m_additionalGpgKeys); if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->m_additionalGpgKeys = dlg->keys(); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count())); } delete dlg; } void KMyMoneyApp::slotKeySelected(int idx) { int cnt = 0; if (idx != 0) { cnt = d->m_additionalGpgKeys.count(); } d->m_additionalKeyLabel->setEnabled(idx != 0); d->m_additionalKeyButton->setEnabled(idx != 0); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", cnt)); } bool KMyMoneyApp::slotFileSaveAs() { bool rc = false; - // in event of it being a database, ensure that all data is read into storage for saveas - if (d->m_myMoneyView->isDatabase()) - dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); KMSTATUS(i18n("Saving file with a new filename...")); // fill the additional key list with the default d->m_additionalGpgKeys = KMyMoneyGlobalSettings::gpgRecipientList(); QWidget* vbox = new QWidget(); QVBoxLayout *vboxVBoxLayout = new QVBoxLayout(vbox); vboxVBoxLayout->setMargin(0); if (KGPGFile::GPGAvailable()) { QWidget* keyBox = new QWidget(vbox); QHBoxLayout *keyBoxHBoxLayout = new QHBoxLayout(keyBox); keyBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(keyBox); QLabel *keyLabel = new QLabel(i18n("Encryption key to be used"), keyBox); keyBoxHBoxLayout->addWidget(keyLabel); d->m_saveEncrypted = new KComboBox(keyBox); keyBoxHBoxLayout->addWidget(d->m_saveEncrypted); QWidget* labelBox = new QWidget(vbox); QHBoxLayout *labelBoxHBoxLayout = new QHBoxLayout(labelBox); labelBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(labelBox); d->m_additionalKeyLabel = new QLabel(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count()), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyLabel); d->m_additionalKeyButton = new QPushButton(i18n("Manage additional keys"), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyButton); connect(d->m_additionalKeyButton, SIGNAL(clicked()), this, SLOT(slotManageGpgKeys())); connect(d->m_saveEncrypted, SIGNAL(activated(int)), this, SLOT(slotKeySelected(int))); // fill the secret key list and combo box QStringList keyList; KGPGFile::secretKeyList(keyList); d->m_saveEncrypted->addItem(i18n("No encryption")); for (QStringList::iterator it = keyList.begin(); it != keyList.end(); ++it) { QStringList fields = (*it).split(':', QString::SkipEmptyParts); if (fields[0] != recoveryKeyId) { // replace parenthesis in name field with brackets auto name = fields[1]; name.replace('(', "["); name.replace(')', "]"); name = QString("%1 (0x%2)").arg(name).arg(fields[0]); d->m_saveEncrypted->addItem(name); if (name.contains(KMyMoneyGlobalSettings::gpgRecipient())) { d->m_saveEncrypted->setCurrentItem(name); } } } } QString prevDir; // don't prompt file name if not a native file if (d->m_myMoneyView->isNativeFile()) prevDir = readLastUsedDir(); QPointer dlg = new QFileDialog(this, i18n("Save As"), prevDir, QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); dlg->setAcceptMode(QFileDialog::AcceptSave); connect(dlg, SIGNAL(filterChanged(QString)), this, SLOT(slotFileSaveAsFilterChanged(QString))); if (dlg->exec() == QDialog::Accepted && dlg != 0) { QUrl newURL = dlg->selectedUrls().first(); if (!newURL.fileName().isEmpty()) { d->consistencyCheck(false); // deleting the dialog will delete the combobox pointed to by d->m_saveEncrypted so get the key name here QString selectedKeyName; if (d->m_saveEncrypted && d->m_saveEncrypted->currentIndex() != 0) selectedKeyName = d->m_saveEncrypted->currentText(); d->m_saveEncrypted = 0; QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); // append extension if not present if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) newName.append(QLatin1String(".kmy")); newURL = QUrl::fromUserInput(newName); d->m_recentFiles->addUrl(newURL); setEnabled(false); // If this is the anonymous file export, just save it, don't actually take the // name, or remember it! Don't even try to encrypt it if (newName.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) rc = d->m_myMoneyView->saveFile(newURL); else { d->m_fileName = newURL; QString encryptionKeys; QRegExp keyExp(".* \\((.*)\\)"); if (keyExp.indexIn(selectedKeyName) != -1) { encryptionKeys = keyExp.cap(1); } if (!d->m_additionalGpgKeys.isEmpty()) { if (!encryptionKeys.isEmpty()) encryptionKeys.append(QLatin1Char(',')); encryptionKeys.append(d->m_additionalGpgKeys.join(QLatin1Char(','))); } rc = d->m_myMoneyView->saveFile(d->m_fileName, encryptionKeys); //write the directory used for this file as the default one for next time. writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); writeLastUsedFile(newName); } d->m_autoSaveTimer->stop(); setEnabled(true); } } delete dlg; updateCaption(); return rc; } void KMyMoneyApp::slotSaveAsDatabase() { saveAsDatabase(); } bool KMyMoneyApp::saveAsDatabase() { bool rc = false; QUrl oldUrl; // in event of it being a database, ensure that all data is read into storage for saveas - if (d->m_myMoneyView->isDatabase()) { - dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); + if (d->m_myMoneyView->isDatabase()) oldUrl = d->m_fileName.isEmpty() ? lastOpenedURL() : d->m_fileName; - } + KMSTATUS(i18n("Saving file to database...")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly); QUrl url = oldUrl; if (!dialog->checkDrivers()) { delete dialog; return (false); } while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) { url = dialog->selectedURL(); // If the protocol is SQL for the old and new, and the hostname and database names match // Let the user know that the current database cannot be saved on top of itself. if (url.scheme() == "sql" && oldUrl.scheme() == "sql" && oldUrl.host() == url.host() && QUrlQuery(oldUrl).queryItemValue("driver") == QUrlQuery(url).queryItemValue("driver") && oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) { KMessageBox::sorry(this, i18n("Cannot save to current database.")); } else { try { rc = d->m_myMoneyView->saveAsDatabase(url); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot save to current database: %1", e.what())); } } } delete dialog; if (rc) { //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); //if(p) d->m_recentFiles->addUrl(url); writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); } d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileCloseWindow() { KMSTATUS(i18n("Closing window...")); if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } close(); } void KMyMoneyApp::slotFileClose() { bool okToSelect = true; // check if transaction editor is open and ask user what he wants to do // slotTransactionsCancelOrEnter(okToSelect); if (!okToSelect) return; // no update status here, as we might delete the status too early. if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } d->closeFile(); } void KMyMoneyApp::slotFileQuit() { // don't modify the status message here as this will prevent quit from working!! // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 bool quitApplication = true; QList memberList = KMainWindow::memberList(); if (!memberList.isEmpty()) { QList::const_iterator w_it = memberList.constBegin(); for (; w_it != memberList.constEnd(); ++w_it) { // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog, // the window and the application stay open. if (!(*w_it)->close()) { quitApplication = false; break; } } } // We will only quit if all windows were processed and not cancelled if (quitApplication) { QCoreApplication::quit(); } } void KMyMoneyApp::slotShowTransactionDetail() { } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneyGlobalSettings::setHideReconciledTransactions(pActions[Action::ViewHideReconciled]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneyGlobalSettings::setHideUnusedCategory(pActions[Action::ViewHideCategories]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { KMyMoneyGlobalSettings::setShowAllAccounts(pActions[Action::ViewShowAll]->isChecked()); d->m_myMoneyView->slotRefreshViews(); } #ifdef KMM_DEBUG +void KMyMoneyApp::slotFileFileInfo() +{ + if (!d->m_myMoneyView->fileOpen()) { + KMessageBox::information(this, i18n("No KMyMoneyFile open")); + return; + } + + QFile g("kmymoney.dump"); + g.open(QIODevice::WriteOnly); + QDataStream st(&g); + MyMoneyStorageDump dumper; + dumper.writeStream(st, MyMoneyFile::instance()->storage()); + g.close(); +} + void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(pActions[Action::DebugTraces]->isChecked() ? 1 : 0); } #endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = pActions[Action::DebugTimers]->isChecked(); } QString KMyMoneyApp::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently QString previousMessage = d->m_statusLabel->text(); d->m_applicationIsReady = false; QString currentMessage = text; if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) { d->m_applicationIsReady = true; currentMessage = i18nc("Application is ready to use", "Ready."); } statusBar()->clearMessage(); d->m_statusLabel->setText(currentMessage); return previousMessage; } void KMyMoneyApp::ready() { slotStatusMsg(QString()); } bool KMyMoneyApp::isReady() { return d->m_applicationIsReady; } void KMyMoneyApp::slotStatusProgressBar(int current, int total) { if (total == -1 && current == -1) { // reset if (d->m_progressTimer) { d->m_progressTimer->start(500); // remove from screen in 500 msec d->m_progressBar->setValue(d->m_progressBar->maximum()); } } else if (total != 0) { // init d->m_progressTimer->stop(); d->m_progressBar->setMaximum(total); d->m_progressBar->setValue(0); d->m_progressBar->show(); } else { // update QTime currentTime = QTime::currentTime(); // only process painting if last update is at least 250 ms ago if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 250) { d->m_progressBar->setValue(current); d->m_lastUpdate = currentTime; } } } void KMyMoneyApp::slotStatusProgressDone() { d->m_progressTimer->stop(); d->m_progressBar->reset(); d->m_progressBar->hide(); d->m_progressBar->setValue(0); } void KMyMoneyApp::progressCallback(int current, int total, const QString& msg) { if (!msg.isEmpty()) kmymoney->slotStatusMsg(msg); kmymoney->slotStatusProgressBar(current, total); } void KMyMoneyApp::slotFileViewPersonal() { if (!d->m_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } KMSTATUS(i18n("Viewing personal data...")); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyPayee user = file->user(); QPointer editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(), user.city(), user.state(), user.postcode(), user.telephone(), user.email(), this, i18n("Edit Personal Data")); if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) { user.setName(editPersonalDataDlg->userName()); user.setAddress(editPersonalDataDlg->userStreet()); user.setCity(editPersonalDataDlg->userTown()); user.setState(editPersonalDataDlg->userCountry()); user.setPostcode(editPersonalDataDlg->userPostcode()); user.setTelephone(editPersonalDataDlg->userTelephone()); user.setEmail(editPersonalDataDlg->userEmail()); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", e.what())); } } delete editPersonalDataDlg; } -void KMyMoneyApp::slotFileFileInfo() -{ - if (!d->m_myMoneyView->fileOpen()) { - KMessageBox::information(this, i18n("No KMyMoneyFile open")); - return; - } - - QFile g("kmymoney.dump"); - g.open(QIODevice::WriteOnly); - QDataStream st(&g); - MyMoneyStorageDump dumper; - dumper.writeStream(st, dynamic_cast(MyMoneyFile::instance()->storage())); - g.close(); -} - void KMyMoneyApp::slotLoadAccountTemplates() { KMSTATUS(i18n("Importing account templates.")); int rc; QPointer dlg = new KLoadTemplateDlg(); if ((rc = dlg->exec()) == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { // import the account templates QList templates = dlg->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to import template(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } delete dlg; } void KMyMoneyApp::slotSaveAccountTemplates() { KMSTATUS(i18n("Exporting account templates.")); QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name(); QDir d(savePath); if (!d.exists()) d.mkpath(savePath); QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath, i18n("KMyMoney template files (*.kmt);;All files (*)")); // // If there is no file extension, then append a .kmt at the end of the file name. // If there is a file extension, make sure it is .kmt, delete any others. // if (!newName.isEmpty()) { // find last . delimiter int nLoc = newName.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = newName.left(nLoc + 1); strExt = newName.right(newName.length() - (nLoc + 1)); if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) { strTemp.append("kmt"); //append to make complete file name newName = strTemp; } } else { newName.append(".kmt"); } if (okToWriteFile(QUrl::fromLocalFile(newName))) { QPointer dlg = new KTemplateExportDlg(this); if (dlg->exec() == QDialog::Accepted && dlg) { MyMoneyTemplate templ; templ.setTitle(dlg->title()); templ.setShortDescription(dlg->shortDescription()); templ.setLongDescription(dlg->longDescription()); templ.exportTemplate(&progressCallback); templ.saveTemplate(QUrl::fromLocalFile(newName)); } delete dlg; } } } bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; if (KMyMoneyUtils::fileExists(url)) { if (KMessageBox::warningYesNo(this, QLatin1String("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String(""), i18n("File already exists")) != KMessageBox::Yes) reallySaveFile = false; } return reallySaveFile; } void KMyMoneyApp::slotSettings() { // if we already have an instance of the settings dialog, then use it if (KConfigDialog::showDialog("KMyMoney-Settings")) return; // otherwise, we have to create it auto dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneyGlobalSettings::self()); connect(dlg, &KSettingsKMyMoney::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotShowCredits() { KAboutData aboutData = initializeCreditsData(); KAboutApplicationDialog dlg(aboutData, this); dlg.exec(); } void KMyMoneyApp::slotUpdateConfiguration(const QString &dialogName) { if(dialogName.compare(QLatin1String("Plugins")) == 0) { KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Reorganize, d->m_plugins, this, guiFactory()); onlineJobAdministration::instance()->updateActions(); return; } MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); #ifdef ENABLE_UNFINISHEDFEATURES LedgerSeparator::setFirstFiscalDate(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); #endif d->m_myMoneyView->updateViewType(); // update the sql storage module settings MyMoneyStorageSql::setStartDate(KMyMoneyGlobalSettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneyGlobalSettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneyGlobalSettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneyGlobalSettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneyGlobalSettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->m_myMoneyView->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setThemedCSS(); // check if the recovery key is still valid or expires soon if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { if (KGPGFile::GPGAvailable()) { KGPGFile file; QDateTime expirationDate = file.keyExpires(QLatin1String(recoveryKeyId)); if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { bool skipMessage = false; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp; QDate lastWarned; if (kconfig) { grp = d->m_config->group("General Options"); lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); if (QDate::currentDate() == lastWarned) { skipMessage = true; } } if (!skipMessage) { if (kconfig) { grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); } KMessageBox::information(this, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); } } } } } void KMyMoneyApp::slotBackupFile() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->m_myMoneyView->dirty()) { if (KMessageBox::questionYesNo(this, i18n("The file must be saved first " "before it can be backed up. Do you want to continue?")) == KMessageBox::No) { return; } slotFileSave(); } if (d->m_fileName.isEmpty()) return; if (!d->m_fileName.isLocalFile()) { KMessageBox::sorry(this, i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_fileName.url()), i18n("Local files only")); return; } QPointer backupDlg = new KBackupDlg(this); int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBox(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->mountPoint(); if (d->m_backupMount) { slotBackupMount(); } else { progressCallback(0, 300, ""); #ifdef Q_OS_WIN d->m_ignoreBackupExitCode = true; QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents())); #else // If we don't have to mount a device, we just issue // a dummy command to start the copy operation d->m_proc.setProgram("true"); d->m_proc.start(); #endif } } delete backupDlg; } void KMyMoneyApp::slotBackupMount() { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } bool KMyMoneyApp::slotBackupWriteFile() { QFileInfo fi(d->m_fileName.fileName()); QString today = QDate::currentDate().toString("-yyyy-MM-dd.") + fi.suffix(); QString backupfile = d->m_mountpoint + '/' + d->m_fileName.fileName(); KMyMoneyUtils::appendCorrectFileExt(backupfile, today); // check if file already exists and ask what to do QFile f(backupfile); if (f.exists()) { int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace"))); if (answer == KMessageBox::Cancel) { return false; } } progressCallback(50, 0, i18n("Writing %1", backupfile)); d->m_proc.clearProgram(); #ifdef Q_OS_WIN d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y"; d->m_proc << (QDir::toNativeSeparators(d->m_fileName.toLocalFile()) + "+ nul") << QDir::toNativeSeparators(backupfile); #else d->m_proc << "cp" << "-f"; d->m_proc << d->m_fileName.toLocalFile() << backupfile; #endif d->m_backupState = BACKUP_COPYING; d->m_proc.start(); return true; } void KMyMoneyApp::slotBackupUnmount() { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } void KMyMoneyApp::slotBackupFinish() { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } void KMyMoneyApp::slotBackupHandleEvents() { switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_ignoreBackupExitCode || (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) { d->m_ignoreBackupExitCode = false; d->m_backupResult = 0; if (!slotBackupWriteFile()) { d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { slotBackupUnmount(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); slotBackupFinish(); } } else { qDebug("copy exit code is %d", d->m_proc.exitCode()); d->m_backupResult = 1; KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup")); if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_UNMOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { progressCallback(300, 0, i18nc("Backup done", "Done")); if (d->m_backupResult == 0) KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); } else { KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup")); } slotBackupFinish(); break; default: qWarning("Unknown state for backup operation!"); progressCallback(-1, -1, QString()); ready(); break; } } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotGenerateSql() { QPointer editor = new KGenerateSqlDlg(this); editor->setObjectName("Generate Database SQL"); editor->exec(); delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneyGlobalSettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::slotFindTransaction() { if (d->m_searchDlg == 0) { d->m_searchDlg = new KFindTransactionDlg(this); connect(d->m_searchDlg, SIGNAL(destroyed()), this, SLOT(slotCloseSearchDialog())); connect(d->m_searchDlg, SIGNAL(transactionSelected(QString,QString)), d->m_myMoneyView, SLOT(slotLedgerSelected(QString,QString))); } d->m_searchDlg->show(); d->m_searchDlg->raise(); d->m_searchDlg->activateWindow(); } void KMyMoneyApp::slotCloseSearchDialog() { if (d->m_searchDlg) d->m_searchDlg->deleteLater(); d->m_searchDlg = 0; } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); try { const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == eMyMoney::Account::Type::Liability) { QString message = i18n("This account is a liability and if the " "opening balance represents money owed, then it should be negative. " "Negate the amount?\n\n" "Please click Yes to change the opening balance to %1,\n" "Please click No to leave the amount as %2,\n" "Please click Cancel to abort the account creation." , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec) , MyMoneyUtils::formatMoney(openingBal, newAccount, sec)); int ans = KMessageBox::questionYesNoCancel(this, message); if (ans == KMessageBox::Yes) { openingBal = -openingBal; } else if (ans == KMessageBox::Cancel) return; } file->createAccount(newAccount, parentAccount, brokerageAccount, openingBal); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", e.what())); } } void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewInvestmentWizard::newInvestment(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { KNewAccountDlg::newCategory(account, parent); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account) { KNewAccountDlg::newCategory(account, MyMoneyAccount()); } void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account) { NewAccountWizard::Wizard::newAccount(account); } void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { MyMoneyFile* file = MyMoneyFile::instance(); // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); } MyMoneyFileTransaction ft; try { file->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.accountType() == eMyMoney::Account::Type::Loan || newAccount.accountType() == eMyMoney::Account::Type::AssetLoan) { newAccount.setValue("schedule", newSchedule.id()); file->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } } QList > KMyMoneyApp::Private::automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount) { static const int NR_OF_STEPS_LIMIT = 300000; static const int PROGRESSBAR_STEPS = 1000; QList > result = transactions; KMSTATUS(i18n("Running automatic reconciliation")); int progressBarIndex = 0; kmymoney->slotStatusProgressBar(progressBarIndex, NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS); // optimize the most common case - all transactions should be cleared QListIterator > itTransactionSplitResult(result); MyMoneyMoney transactionsBalance; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); transactionsBalance += transactionSplit.second.shares(); } if (amount == transactionsBalance) { result = transactions; return result; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // only one transaction is uncleared itTransactionSplitResult.toFront(); int index = 0; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); if (transactionsBalance - transactionSplit.second.shares() == amount) { result.removeAt(index); return result; } index++; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // more than one transaction is uncleared - apply the algorithm result.clear(); const MyMoneySecurity &security = MyMoneyFile::instance()->security(account.currencyId()); double precision = 0.1 / account.fraction(security); QList sumList; sumList << MyMoneyMoney(); QMap > > sumToComponentsMap; // compute the possible matches QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); QListIterator itSum(sumList); QList tempList; while (itSum.hasNext()) { const MyMoneyMoney &sum = itSum.next(); QList > splitIds; splitIds << qMakePair(transactionSplit.first.id(), transactionSplit.second.id()); if (sumToComponentsMap.contains(sum)) { if (sumToComponentsMap.value(sum).contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { continue; } splitIds.append(sumToComponentsMap.value(sum)); } tempList << transactionSplit.second.shares() + sum; sumToComponentsMap[transactionSplit.second.shares() + sum] = splitIds; int size = sumToComponentsMap.size(); if (size % PROGRESSBAR_STEPS == 0) { kmymoney->slotStatusProgressBar(progressBarIndex++, 0); } if (size > NR_OF_STEPS_LIMIT) { return result; // it's taking too much resources abort the algorithm } } QList unionList; unionList.append(tempList); unionList.append(sumList); qSort(unionList); sumList.clear(); MyMoneyMoney smallestSumFromUnion = unionList.first(); sumList.append(smallestSumFromUnion); QListIterator itUnion(unionList); while (itUnion.hasNext()) { MyMoneyMoney sumFromUnion = itUnion.next(); if (smallestSumFromUnion < MyMoneyMoney(1 - precision / transactions.size())*sumFromUnion) { smallestSumFromUnion = sumFromUnion; sumList.append(sumFromUnion); } } } kmymoney->slotStatusProgressBar(NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS, 0); if (sumToComponentsMap.contains(amount)) { QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); const QList > &splitIds = sumToComponentsMap.value(amount); if (splitIds.contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { result.append(transactionSplit); } } } #ifdef KMM_DEBUG qDebug("For the amount %s a number of %d possible sums where computed from the set of %d transactions: ", qPrintable(MyMoneyUtils::formatMoney(amount, security)), sumToComponentsMap.size(), transactions.size()); #endif kmymoney->slotStatusProgressBar(-1, -1); return result; } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyInstitution& _dst) { MyMoneyAccount src(_src); src.setInstitutionId(_dst.id()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(src); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to institution %2. Reason: %3

", src.name(), _dst.name(), e.what())); } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyAccount& _dst) { MyMoneyAccount src(_src); MyMoneyAccount dst(_dst); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->reparentAccount(src, dst); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to %2. Reason: %3

", src.name(), dst.name(), e.what())); } } void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) { KEditScheduleDlg::newSchedule(_t, occurrence); } void KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { KMyMoneyUtils::newPayee(newnameBase, id); } void KMyMoneyApp::slotNewFeature() { } // move a stock transaction from one investment account to another void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/, const QString& toId, const MyMoneyTransaction& tx) { MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); MyMoneyTransaction t(tx); // first determine which stock we are dealing with. // fortunately, investment transactions have only one stock involved QString stockAccountId; QString stockSecurityId; MyMoneySplit s; foreach (const auto split, t.splits()) { stockAccountId = split.accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = split; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; foreach (const auto sAccount, toInvAcc.accountList()) { if (MyMoneyFile::instance()->account(sAccount).currencyId() == stockSecurityId) { newStockAccountId = sAccount; break; } } // if it doesn't exist, we need to add it as a copy of the old one // no 'copyAccount()' function?? if (newStockAccountId.isEmpty()) { MyMoneyAccount stockAccount = MyMoneyFile::instance()->account(stockAccountId); MyMoneyAccount newStock; newStock.setName(stockAccount.name()); newStock.setNumber(stockAccount.number()); newStock.setDescription(stockAccount.description()); newStock.setInstitutionId(stockAccount.institutionId()); newStock.setOpeningDate(stockAccount.openingDate()); newStock.setAccountType(stockAccount.accountType()); newStock.setCurrencyId(stockAccount.currencyId()); newStock.setClosed(stockAccount.isClosed()); MyMoneyFile::instance()->addAccount(newStock, toInvAcc); newStockAccountId = newStock.id(); } // now update the split and the transaction s.setAccountId(newStockAccountId); t.modifySplit(s); MyMoneyFile::instance()->modifyTransaction(t); } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); QMenu *menu = dynamic_cast(w); if (menu) menu->exec(QCursor::pos()); else qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu); } void KMyMoneyApp::slotPrintView() { d->m_myMoneyView->slotPrintView(); } void KMyMoneyApp::updateCaption(bool skipActions) { QString caption; caption = d->m_fileName.fileName(); if (caption.isEmpty() && d->m_myMoneyView && d->m_myMoneyView->fileOpen()) caption = i18n("Untitled"); // MyMoneyFile::instance()->dirty() throws an exception, if // there's no storage object available. In this case, we // assume that the storage object is not changed. Actually, // this can only happen if we are newly created early on. bool modified; try { modified = MyMoneyFile::instance()->dirty(); } catch (const MyMoneyException &) { modified = false; skipActions = true; } #ifdef KMM_DEBUG caption += QString(" (%1 x %2)").arg(width()).arg(height()); #endif setCaption(caption, modified); if (!skipActions) { d->m_myMoneyView->enableViewsIfFileOpen(); slotUpdateActions(); } } void KMyMoneyApp::slotUpdateActions() { const auto file = MyMoneyFile::instance(); const bool fileOpen = d->m_myMoneyView->fileOpen(); const bool modified = file->dirty(); // const bool importRunning = (d->m_smtReader != 0); auto aC = actionCollection(); // ************* // Disabling actions to be disabled at this point // ************* { static const QVector disabledActions { Action::UpdateAllAccounts }; for (const auto& a : disabledActions) pActions[a]->setEnabled(false); } // ************* // Disabling actions based on conditions // ************* { QString tooltip = i18n("Create a new transaction"); const QVector> actionStates { {qMakePair(Action::FileOpenDatabase, true)}, {qMakePair(Action::FileSaveAsDatabase, fileOpen)}, {qMakePair(Action::FilePersonalData, fileOpen)}, {qMakePair(Action::FileBackup, (fileOpen && !d->m_myMoneyView->isDatabase()))}, {qMakePair(Action::FileInformation, fileOpen)}, {qMakePair(Action::FileImportTemplate, fileOpen/* && !importRunning*/)}, {qMakePair(Action::FileExportTemplate, fileOpen/* && !importRunning*/)}, #ifdef KMM_DEBUG {qMakePair(Action::FileDump, fileOpen)}, #endif {qMakePair(Action::EditFindTransaction, fileOpen)}, {qMakePair(Action::ToolCurrencies, fileOpen)}, {qMakePair(Action::ToolPrices, fileOpen)}, {qMakePair(Action::ToolUpdatePrices, fileOpen)}, {qMakePair(Action::ToolConsistency, fileOpen)}, {qMakePair(Action::NewAccount, fileOpen)}, {qMakePair(Action::AccountCreditTransfer, onlineJobAdministration::instance()->canSendCreditTransfer())}, {qMakePair(Action::NewInstitution, fileOpen)}, // {qMakePair(Action::TransactionNew, (fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)))}, {qMakePair(Action::NewSchedule, fileOpen)}, // {qMakePair(Action::CurrencyNew, fileOpen)}, // {qMakePair(Action::PriceNew, fileOpen)}, }; for (const auto& a : actionStates) pActions[a.first]->setEnabled(a.second); } // ************* // Disabling standard actions based on conditions // ************* - aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(modified && !d->m_myMoneyView->isDatabase()); + aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(modified /*&& !d->m_myMoneyView->isDatabase()*/); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print)))->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); // ************* // Enabling actions based on conditions // ************* QList accList; file->accountList(accList); QList::const_iterator it_a; QMap::const_iterator it_p = d->m_plugins.online.constEnd(); for (it_a = accList.constBegin(); (it_p == d->m_plugins.online.constEnd()) && (it_a != accList.constEnd()); ++it_a) { if ((*it_a).hasOnlineMapping()) { // check if provider is available it_p = d->m_plugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower()); if (it_p != d->m_plugins.online.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { pActions[Action::UpdateAllAccounts]->setEnabled(true); } } } } } void KMyMoneyApp::slotResetSelections() { slotSelectAccount(MyMoneyAccount()); d->m_myMoneyView->slotObjectSelected(MyMoneyAccount()); d->m_myMoneyView->slotObjectSelected(MyMoneyInstitution()); d->m_myMoneyView->slotObjectSelected(MyMoneySchedule()); d->m_myMoneyView->slotObjectSelected(MyMoneyTag()); d->m_myMoneyView->slotTransactionsSelected(KMyMoneyRegister::SelectedTransactions()); slotUpdateActions(); } void KMyMoneyApp::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; d->m_selectedAccount = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (!acc.isInvest()) d->m_selectedAccount = acc; // qDebug("slotSelectAccount('%s')", d->m_selectedAccount.name().data()); slotUpdateActions(); emit accountSelected(d->m_selectedAccount); } void KMyMoneyApp::slotDataChanged() { // As this method is called every time the MyMoneyFile instance // notifies a modification, it's the perfect place to start the timer if needed if (d->m_autoSaveEnabled && !d->m_autoSaveTimer->isActive()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); //miliseconds } updateCaption(); } void KMyMoneyApp::slotCurrencyDialog() { QPointer dlg = new KCurrencyEditDlg(this); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); dlg->exec(); delete dlg; } void KMyMoneyApp::slotFileConsistencyCheck() { d->consistencyCheck(true); updateCaption(); } void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult) { KMSTATUS(i18n("Running consistency check...")); MyMoneyFileTransaction ft; try { m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck(); ft.commit(); } catch (const MyMoneyException &e) { m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what())); // always display the result if the check failed alwaysDisplayResult = true; } // in case the consistency check was OK, we get a single line as result // in all errneous cases, we get more than one line and force the // display of them. if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) { QString msg = i18n("The consistency check has found no issues in your data. Details are presented below."); if (m_consistencyCheckResult.size() > 1) msg = i18n("The consistency check has found some issues in your data. Details are presented below. Those issues that could not be corrected automatically need to be solved by the user."); // install a context menu for the list after the dialog is displayed QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu())); KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result")); } // this data is no longer needed m_consistencyCheckResult.clear(); } void KMyMoneyApp::Private::copyConsistencyCheckResults() { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n"))); } void KMyMoneyApp::Private::saveConsistencyCheckResults() { QUrl fileUrl = QFileDialog::getSaveFileUrl(q); if (!fileUrl.isEmpty()) { QFile file(fileUrl.toLocalFile()); if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) { QTextStream out(&file); out << m_consistencyCheckResult.join(QLatin1String("\n")); file.close(); } } } void KMyMoneyApp::Private::setThemedCSS() { const QStringList CSSnames {QStringLiteral("kmymoney.css"), QStringLiteral("welcome.css")}; const QString rcDir("/html/"); const QStringList defaultCSSDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, rcDir, QStandardPaths::LocateDirectory); // scan the list of directories to find the ones that really // contains all files we look for QString defaultCSSDir; foreach (const auto dir, defaultCSSDirs) { defaultCSSDir = dir; foreach (const auto CSSname, CSSnames) { QFileInfo fileInfo(defaultCSSDir + CSSname); if (!fileInfo.exists()) { defaultCSSDir.clear(); break; } } if (!defaultCSSDir.isEmpty()) { break; } } // make sure we have the local directory where the themed version is stored const QString themedCSSDir = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation).first() + rcDir; QDir().mkpath(themedCSSDir); foreach (const auto CSSname, CSSnames) { const QString defaultCSSFilename = defaultCSSDir + CSSname; QFileInfo fileInfo(defaultCSSFilename); if (fileInfo.exists()) { const QString themedCSSFilename = themedCSSDir + CSSname; QFile::remove(themedCSSFilename); if (QFile::copy(defaultCSSFilename, themedCSSFilename)) { QFile cssFile (themedCSSFilename); if (cssFile.open(QIODevice::ReadWrite)) { QTextStream cssStream(&cssFile); auto cssText = cssStream.readAll(); cssText.replace(QLatin1String("./"), defaultCSSDir, Qt::CaseSensitive); cssText.replace(QLatin1String("WindowText"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::WindowText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Window"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::WindowBackground).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("HighlightText"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListHighlightText).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("Highlight"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListHighlight).name(), Qt::CaseSensitive); cssText.replace(QLatin1String("black"), KMyMoneyGlobalSettings::schemeColor(SchemeColor::ListGrid).name(), Qt::CaseSensitive); cssStream.seek(0); cssStream << cssText; cssFile.close(); } } } } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneyGlobalSettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneyGlobalSettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; eDialogs::ScheduleResultCode rc = eDialogs::ScheduleResultCode::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != eDialogs::ScheduleResultCode::Cancel); ++it) { // Get the copy in the file because it might be modified by commitTransaction MyMoneySchedule schedule = file->schedule((*it).id()); if (schedule.autoEnter()) { try { while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) && rc != eDialogs::ScheduleResultCode::Ignore && rc != eDialogs::ScheduleResultCode::Cancel) { rc = d->m_myMoneyView->enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == eDialogs::ScheduleResultCode::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = eDialogs::ScheduleResultCode::Enter; } } updateCaption(); } } void KMyMoneyApp::writeLastUsedDir(const QString& directory) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = kconfig->group("General Options"); //write path entry, no error handling since its void. grp.writeEntry("LastUsedDirectory", directory); } } void KMyMoneyApp::writeLastUsedFile(const QString& fileName) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // write path entry, no error handling since its void. // use a standard string, as fileName could contain a protocol // e.g. file:/home/thb/.... grp.writeEntry("LastUsedFile", fileName); } } QString KMyMoneyApp::readLastUsedDir() const { QString str; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); //read path entry. Second parameter is the default if the setting is not found, which will be the default document path. str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); // if the path stored is empty, we use the default nevertheless if (str.isEmpty()) str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } return str; } QString KMyMoneyApp::readLastUsedFile() const { QString str; // get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // read filename entry. str = grp.readEntry("LastUsedFile", ""); } return str; } QString KMyMoneyApp::filename() const { return d->m_fileName.url(); } WebConnect* KMyMoneyApp::webConnect() const { return d->m_webConnect; } QList KMyMoneyApp::instanceList() const { QList list; #ifdef KMM_DBUS QDBusReply reply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); if (reply.isValid()) { QStringList apps = reply.value(); QStringList::ConstIterator it; // build a list of service names of all running kmymoney applications without this one for (it = apps.constBegin(); it != apps.constEnd(); ++it) { // please change this method of creating a list of 'all the other kmymoney instances that are running on the system' // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somwhere if ((*it).indexOf("org.kde.kmymoney-") == 0) { uint thisProcPid = platformTools::processId(); if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0) list += (*it); } } } else { qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message())); } #endif return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id) { // // Web connect attempts to go through the known importers and see if the file // can be importing using that method. If so, it will import it using that // plugin // Q_UNUSED(asn_id) d->m_importUrlsQueue.enqueue(sourceUrl); // only start processing if this is the only import so far if (d->m_importUrlsQueue.count() == 1) { while (!d->m_importUrlsQueue.isEmpty()) { // get the value of the next item from the queue // but leave it on the queue for now QString url = d->m_importUrlsQueue.head(); // Bring this window to the forefront. This method was suggested by // Lubos Lunak of the KDE core development team. // TODO: port KF5 (WebConnect) //KStartupInfo::setNewStartupId(this, asn_id); // Make sure we have an open file if (! d->m_myMoneyView->fileOpen() && KMessageBox::warningContinueCancel(kmymoney, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue) kmymoney->slotFileOpen(); // only continue if the user really did open a file. if (d->m_myMoneyView->fileOpen()) { KMSTATUS(i18n("Importing a statement via Web Connect")); // remove the statement files d->unlinkStatementXML(); QMap::const_iterator it_plugin = d->m_plugins.importer.constBegin(); while (it_plugin != d->m_plugins.importer.constEnd()) { if ((*it_plugin)->isMyFormat(url)) { QList statements; if (!(*it_plugin)->import(url)) { KMessageBox::error(this, i18n("Unable to import %1 using %2 plugin. The plugin returned the following error: %3", url, (*it_plugin)->formatName(), (*it_plugin)->lastError()), i18n("Importing error")); } break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_plugins.importer.constEnd()) if (MyMoneyStatement::isStatementFile(url)) MyMoneyStatementReader::importStatement(url, false, &progressCallback); } // remove the current processed item from the queue d->m_importUrlsQueue.dequeue(); } } } void KMyMoneyApp::slotEnableMessages() { KMessageBox::enableAllMessages(); KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages")); } void KMyMoneyApp::createInterfaces() { // Sets up the plugin interface KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this); KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this); KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(d->m_myMoneyView, this); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->m_myMoneyView->dirty() && d->m_autoSaveEnabled) { if (!slotFileSave() && d->m_autoSavePeriod > 0) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } } d->m_inAutoSaving = false; if (focusWidget && focusWidget != qApp->focusWidget()) { // we have a valid focus widget so restore it focusWidget->setFocus(); } } } void KMyMoneyApp::slotDateChanged() { QDateTime dt = QDateTime::currentDateTime(); QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0)); // +1 is to make sure that we're already in the next day when the // signal is sent (this way we also avoid setting the timer to 0) QTimer::singleShot((dt.secsTo(nextDay) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotOnlineAccountRequested(const MyMoneyAccount& acc, eMenu::Action action) { d->m_selectedAccount = acc; switch (action) { case eMenu::Action::UnmapOnlineAccount: slotAccountUnmapOnline(); break; case eMenu::Action::MapOnlineAccount: slotAccountMapOnline(); break; case eMenu::Action::UpdateAccount: slotAccountUpdateOnline(); break; case eMenu::Action::UpdateAllAccounts: slotAccountUpdateOnlineAll(); break; default: break; } } void KMyMoneyApp::slotAccountUnmapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // not a mapped account if (!d->m_selectedAccount.hasOnlineMapping()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_selectedAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_selectedAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_selectedAccount.deletePair("StatementKey"); MyMoneyFile::instance()->modifyAccount(d->m_selectedAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", e.what())); } } } void KMyMoneyApp::slotAccountMapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // already an account mapped if (d->m_selectedAccount.hasOnlineMapping()) return; // check if user tries to map a brokerageAccount if (d->m_selectedAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_plugins.online.count()) { case 0: break; case 1: provider = d->m_plugins.online.begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_plugins.online.constBegin(); it_p != d->m_plugins.online.constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_plugins.online.constFind(provider.toLower()); if (it_p != d->m_plugins.online.constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) { settings["provider"] = provider.toLower(); MyMoneyAccount acc(d->m_selectedAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", e.what())); } } } } void KMyMoneyApp::slotAccountUpdateOnlineAll() { QList accList; MyMoneyFile::instance()->accountList(accList); QList::iterator it_a; QMap::const_iterator it_p; d->m_statementResults.clear(); d->m_collectingStatements = true; // remove all those from the list, that don't have a 'provider' or the // provider is not currently present for (it_a = accList.begin(); it_a != accList.end();) { if (!(*it_a).hasOnlineMapping() || d->m_plugins.online.find((*it_a).onlineBankingSettings().value("provider").toLower()) == d->m_plugins.online.end()) { it_a = accList.erase(it_a); } else ++it_a; } const QVector disabledActions {Action::UpdateAccount, Action::UpdateAllAccounts}; for (const auto& a : disabledActions) pActions[a]->setEnabled(false); // now work on the remaining list of accounts int cnt = accList.count() - 1; for (it_a = accList.begin(); it_a != accList.end(); ++it_a) { it_p = d->m_plugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower()); (*it_p)->updateAccount(*it_a, cnt != 0); --cnt; } d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotAccountUpdateOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // no online account mapped if (!d->m_selectedAccount.hasOnlineMapping()) return; const QVector disabledActions {Action::UpdateAccount, Action::UpdateAllAccounts}; for (const auto& a : disabledActions) pActions[a]->setEnabled(false); // find the provider QMap::const_iterator it_p; it_p = d->m_plugins.online.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider").toLower()); if (it_p != d->m_plugins.online.constEnd()) { // plugin found, call it d->m_collectingStatements = true; d->m_statementResults.clear(); (*it_p)->updateAccount(d->m_selectedAccount); d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); } // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion) { #ifdef KF5Holidays_FOUND //since the cost of updating the cache is now not negligible //check whether the region has been modified if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) { // Delete the previous holidayRegion before creating a new one. delete d->m_holidayRegion; // Create a new holidayRegion. d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion); //clear and update the holiday cache preloadHolidays(); } #else Q_UNUSED(holidayRegion); #endif } bool KMyMoneyApp::isProcessingDate(const QDate& date) const { #ifdef KF5Holidays_FOUND if (!d->m_processingDays.testBit(date.dayOfWeek())) return false; if (!d->m_holidayRegion || !d->m_holidayRegion->isValid()) return true; //check first whether it's already in cache if (d->m_holidayMap.contains(date)) { return d->m_holidayMap.value(date, true); } else { bool processingDay = !d->m_holidayRegion->isHoliday(date); d->m_holidayMap.insert(date, processingDay); return processingDay; } #else Q_UNUSED(date); return true; #endif } void KMyMoneyApp::preloadHolidays() { #ifdef KF5Holidays_FOUND //clear the cache before loading d->m_holidayMap.clear(); //only do this if it is a valid region if (d->m_holidayRegion && d->m_holidayRegion->isValid()) { //load holidays for the forecast days plus 1 cycle, to be on the safe side int forecastDays = KMyMoneyGlobalSettings::forecastDays() + KMyMoneyGlobalSettings::forecastAccountCycle(); QDate endDate = QDate::currentDate().addDays(forecastDays); //look for holidays for the next 2 years as a minimum. That should give a good margin for the cache if (endDate < QDate::currentDate().addYears(2)) endDate = QDate::currentDate().addYears(2); KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate); KHolidays::Holiday::List::const_iterator holiday_it; for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) { for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1)) d->m_holidayMap.insert(holidayDate, false); } for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) { //if it is not a processing day, set it to false if (!d->m_processingDays.testBit(date.dayOfWeek())) { d->m_holidayMap.insert(date, false); } else if (!d->m_holidayMap.contains(date)) { //if it is not a holiday nor a weekend, it is a processing day d->m_holidayMap.insert(date, true); } } } #endif } KMStatus::KMStatus(const QString &text) { m_prevText = kmymoney->slotStatusMsg(text); } KMStatus::~KMStatus() { kmymoney->slotStatusMsg(m_prevText); } void KMyMoneyApp::Private::unlinkStatementXML() { QDir d(KMyMoneySettings::logPath(), "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); d.remove(KMyMoneySettings::logPath() + QString("/%1").arg(d[i])); } m_statementXMLindex = 0; } void KMyMoneyApp::Private::closeFile() { q->slotSelectAccount(MyMoneyAccount()); q->d->m_myMoneyView->slotObjectSelected(MyMoneyAccount()); q->d->m_myMoneyView->slotObjectSelected(MyMoneyInstitution()); q->d->m_myMoneyView->slotObjectSelected(MyMoneySchedule()); q->d->m_myMoneyView->slotObjectSelected(MyMoneyTag()); q->d->m_myMoneyView->slotTransactionsSelected(KMyMoneyRegister::SelectedTransactions()); // q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); m_reconciliationAccount = MyMoneyAccount(); m_myMoneyView->finishReconciliation(MyMoneyAccount()); m_myMoneyView->closeFile(); m_fileName = QUrl(); q->updateCaption(); // just create a new balance warning object delete m_balanceWarning; m_balanceWarning = new KBalanceWarning(q); emit q->fileLoaded(m_fileName); } diff --git a/kmymoney/kmymoney.h b/kmymoney/kmymoney.h index 70509c917..2b5701deb 100644 --- a/kmymoney/kmymoney.h +++ b/kmymoney/kmymoney.h @@ -1,706 +1,706 @@ /*************************************************************************** kmymoney.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEY_H #define KMYMONEY_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include #include "kmymoneyutils.h" #include "mymoneyaccount.h" #include "mymoney/onlinejob.h" #include "onlinejobtyped.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneymoney.h" #include "selectedtransactions.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" class QResizeEvent; class MyMoneyObject; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyPayee; class MyMoneyPrice; class MyMoneyTag; class MyMoneySplit; class MyMoneyTransaction; class WebConnect; class creditTransfer; template class onlineJobTyped; namespace eDialogs { enum class ScheduleResultCode; } namespace eMenu { enum class Action; enum class Menu; } /*! \mainpage KMyMoney Main Page for API documentation. * * \section intro Introduction * * This is the API documentation for KMyMoney. It should be used as a reference * for KMyMoney developers and users who wish to see how KMyMoney works. This * documentation will be kept up-to-date as development progresses and should be * read for new features that have been developed in KMyMoney. */ /** * The base class for KMyMoney application windows. It sets up the main * window and reads the config file as well as providing a menubar, toolbar * and statusbar. * * @see KMyMoneyView * * @author Michael Edwardes 2000-2001 * @author Thomas Baumgart 2006-2008 * * @short Main application class. */ class KMyMoneyApp : public KXmlGuiWindow, public IMyMoneyProcessingCalendar { Q_OBJECT private Q_SLOTS: /** * Keep track of objects that are destroyed by external events */ void slotObjectDestroyed(QObject* o); /** * Add a context menu to the list used by KMessageBox::informationList to display the consistency check results. */ void slotInstallConsistencyCheckContextMenu(); /** * Handle the context menu of the list used by KMessageBox::informationList to display the consistency check results. */ void slotShowContextMenuForConsistencyCheck(const QPoint &); protected Q_SLOTS: void slotFileSaveAsFilterChanged(const QString& filter); /** * This slot is intended to be used as part of auto saving. This is used when the * QTimer emits the timeout signal and simply checks that the file is dirty (has * received modifications to its contents), and call the appropriate method to * save the file. Furthermore, re-starts the timer (possibly not needed). * @author mvillarino 2005 * @see KMyMoneyApp::slotDataChanged() */ void slotAutoSave(); /** * This slot re-enables all message for which the "Don't show again" * option had been selected. */ void slotEnableMessages(); - /** - * Called when the user asks for file information. - */ - void slotFileFileInfo(); - /** * Called to run performance test. */ void slotPerformanceTest(); /** * Called to generate the sql to create kmymoney database tables etc. */ void slotGenerateSql(); #ifdef KMM_DEBUG + /** + * Called when the user asks for file information. + */ + void slotFileFileInfo(); + /** * Debugging only: turn on/off traces */ void slotToggleTraces(); #endif /** * Debugging only: turn on/off timers */ void slotToggleTimers(); /** * Called when the user asks for the personal information. */ void slotFileViewPersonal(); void slotLoadAccountTemplates(); void slotSaveAccountTemplates(); /** * Open up the application wide settings dialog. * * @see KSettingsDlg */ void slotSettings(); /** * Called to show credits window. */ void slotShowCredits(); /** * Called when the user wishes to backup the current file */ void slotBackupFile(); /** * Perform mount operation before making a backup of the current file */ void slotBackupMount(); /** * Perform the backup write operation */ bool slotBackupWriteFile(); /** * Perform unmount operation after making a backup of the current file */ void slotBackupUnmount(); /** * Finish backup of the current file */ void slotBackupFinish(); /** * Handle events on making a backup of the current file */ void slotBackupHandleEvents(); void slotShowTipOfTheDay(); void slotShowPreviousView(); void slotShowNextView(); /** * Brings up a dialog to let the user search for specific transaction(s). It then * opens a results window to display those transactions. */ void slotFindTransaction(); /** * Destroys a possibly open the search dialog */ void slotCloseSearchDialog(); /** * Calls the print logic for the current view */ void slotPrintView(); /** * Call this slot, if any configuration parameter has changed */ void slotUpdateConfiguration(const QString &dialogName); /** * This slot is used to start new features during the development cycle */ void slotNewFeature(); /** * This slot triggers an update of all views and restarts * a single shot timer to call itself again at beginning of * the next day. */ void slotDateChanged(); /** * This slot will be called when the engine data changed * and the application object needs to update its state. */ void slotDataChanged(); /** * This slot collects information for a new scheduled transaction * based on transaction @a t and @a occurrence and saves it in the engine. */ void slotScheduleNew(const MyMoneyTransaction& t, eMyMoney::Schedule::Occurrence occurrence = eMyMoney::Schedule::Occurrence::Monthly); void slotAccountMapOnline(); void slotAccountUnmapOnline(); void slotAccountUpdateOnline(); void slotAccountUpdateOnlineAll(); void slotManageGpgKeys(); void slotKeySelected(int idx); void slotStatusProgressDone(); public: /** * This method checks if there is at least one asset or liability account * in the current storage object. If not, it starts the new account wizard. */ void createInitialAccount(); /** * This method returns the last URL used or an empty URL * depending on the option setting if the last file should * be opened during startup or the open file dialog should * be displayed. * * @return URL of last opened file or empty if the program * should start with the open file dialog */ QUrl lastOpenedURL(); /** * construtor of KMyMoneyApp, calls all init functions to create the application. */ explicit KMyMoneyApp(QWidget* parent = 0); /** * Destructor */ ~KMyMoneyApp(); static void progressCallback(int current, int total, const QString&); void writeLastUsedDir(const QString& directory); QString readLastUsedDir() const; void writeLastUsedFile(const QString& fileName); QString readLastUsedFile() const; /** * Returns whether there is an importer available that can handle this file */ bool isImportableFile(const QUrl &url); /** * This method is used to update the caption of the application window. * It sets the caption to "filename [modified] - KMyMoney". * * @param skipActions if true, the actions will not be updated. This * is usually onyl required by some early calls when * these widgets are not yet created (the default is false). */ void updateCaption(bool skipActions = false); /** * This method returns a list of all 'other' dcop registered kmymoney processes. * It's a subset of the return of DCOPclient()->registeredApplications(). * * @retval QStringList of process ids */ QList instanceList() const; #ifdef KMM_DEBUG /** * Dump a list of the names of all defined KActions to stdout. */ void dumpActions() const; #endif /** * Popup the context menu with the respective @p containerName. * Valid container names are defined in kmymoneyui.rc */ void showContextMenu(const QString& containerName); void createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal); QString filename() const; /** * Checks if the file with the @a url already exists. If so, * the user is asked if he/she wants to override the file. * If the user's answer is negative, @p false will be returned. * @p true will be returned in all other cases. */ bool okToWriteFile(const QUrl &url); /** * Return pointer to the WebConnect object */ WebConnect* webConnect() const; protected: /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration * file */ void saveOptions(); /** * Creates the interfaces necessary for the plugins to work. Therefore, * this method must be called prior to loadPlugins(). */ void createInterfaces(); /** * read general options again and initialize all variables like the recent file list */ void readOptions(); /** * Gets pointers for menus preset by KXMLGUIFactory * @return pointers for menus */ QHash initMenus(); /** * Initializes QActions (names, object names, icons, some connections, shortcuts) * @return pointers for actions */ QHash initActions(); /** initializes the dynamic menus (account selectors) */ void initDynamicMenus(); /** * sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); /** queryClose is called by KMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this calls saveModified() on the document object to ask if the document shall * be saved if Modified; on cancel the closeEvent is rejected. * The settings are saved using saveOptions() if we are about to close. * @see KMainWindow#queryClose * @see QWidget#closeEvent */ virtual bool queryClose(); void slotCheckSchedules(); virtual void resizeEvent(QResizeEvent*); void createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount); /** * This method preloads the holidays for the duration of the default forecast period */ void preloadHolidays(); public Q_SLOTS: void slotFileInfoDialog(); /** */ void slotFileNew(); /** open a file and load it into the document*/ void slotFileOpen(); /** opens a file from the recent files menu */ void slotFileOpenRecent(const QUrl &url); /** open a SQL database */ void slotOpenDatabase(); /** * saves the current document. If it has no name yet, the user * will be queried for it. * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSave(); /** * ask the user for the filename and save the current document * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSaveAs(); /** * ask the user to select a database and save the current document * * @retval false save operation failed * @retval true save operation was successful */ bool saveAsDatabase(); void slotSaveAsDatabase(); /** asks for saving if the file is modified, then closes the actual file and window */ void slotFileCloseWindow(); /** asks for saving if the file is modified, then closes the actual file */ void slotFileClose(); /** * closes all open windows by calling close() on each memberList item * until the list is empty, then quits the application. * If queryClose() returns false because the user canceled the * saveModified() dialog, the closing breaks. */ void slotFileQuit(); void slotFileConsistencyCheck(); /** * fires up the price table editor */ void slotPriceDialog(); /** * fires up the currency table editor */ void slotCurrencyDialog(); /** * dummy method needed just for initialization */ void slotShowTransactionDetail(); /** * Toggles the hide reconciled transactions setting */ void slotHideReconciledTransactions(); /** * Toggles the hide unused categories setting */ void slotHideUnusedCategories(); /** * Toggles the show all accounts setting */ void slotShowAllAccounts(); /** * changes the statusbar contents for the standard label permanently, * used to indicate current actions. Returns the previous value for * 'stacked' usage. * * @param text the text that is displayed in the statusbar */ QString slotStatusMsg(const QString &text); /** * This method changes the progress bar in the status line according * to the parameters @p current and @p total. The following special * cases exist: * * - current = -1 and total = -1 will reset the progress bar * - current = ?? and total != 0 will setup the 100% mark to @p total * - current = xx and total == 0 will set the percentage * * @param current the current value with respect to the initialised * 100% mark * @param total the total value (100%) */ void slotStatusProgressBar(int current, int total = 0); /** * Called to update stock and currency prices from the user menu */ void slotEquityPriceUpdate(); - void slotOnlineAccountRequested(const MyMoneyAccount& acc, eMenu::Action action); + void slotOnlineAccountRequested(const MyMoneyAccount& acc, eMenu::Action action); /** * This slot reparents account @p src to be a child of account @p dest * * @param src account to be reparented * @param dest new parent */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyAccount& dest); /** * This slot reparents account @p src to be a held at institution @p dest * * @param src account to be reparented * @param dest new parent institution */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyInstitution& dest); /** * Create a new investment in a given @p parent investment account */ void slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name and parent account. * * @param account reference of category to be created. The @p name member * should be filled by the caller. The object will be filled * with additional information during the creation process * esp. the @p id member. * @param parent reference to parent account (defaults to none) */ void slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent); void slotCategoryNew(MyMoneyAccount& account); /** */ void slotPayeeNew(const QString& newnameBase, QString& id); /** * This slot fires up the KCalc application */ void slotToolsStartKCalc(); void slotResetSelections(); void slotSelectAccount(const MyMoneyObject& account); /** * Brings up the new account wizard and saves the information. */ void slotAccountNew(MyMoneyAccount&); /** * This method updates all KAction items to the current state. */ void slotUpdateActions(); void webConnect(const QString& sourceUrl, const QByteArray &asn_id); void webConnect(const QUrl url) { webConnect(url.path(), QByteArray()); } private: /** * This method sets the holidayRegion for use by the processing calendar. */ void setHolidayRegion(const QString& holidayRegion); /** * Load the status bar with the 'ready' message. This is hold in a single * place, so that is consistent with isReady(). */ void ready(); /** * Check if the status bar contains the 'ready' message. The return * value is used e.g. to detect if a quit operation is allowed or not. * * @retval true application is idle * @retval false application is active working on a longer operation */ bool isReady(); /** * Re-implemented from IMyMoneyProcessingCalendar */ bool isProcessingDate(const QDate& date) const; /** * Depending on the setting of AutoSaveOnQuit, this method * asks the user to save the file or not. * * @returns see return values of KMessageBox::warningYesNoCancel() */ int askSaveOnClose(); Q_SIGNALS: /** * This signal is emitted when a new file is loaded. In the case file * is closed, this signal is also emitted with an empty url. */ void fileLoaded(const QUrl &url); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is sent out, when the user presses Ctrl+A or activates * the Select all transactions action. */ void selectAllTransactions(); /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& account); /** * This signal is emitted when a new institution has been selected by * the GUI. If no institution is selected or the selection is removed, * @a institution is identical to MyMoneyInstitution(). This signal is used * by plugins to get information about changes. */ void institutionSelected(const MyMoneyInstitution& institution); /** * This signal is emitted when a new schedule has been selected by * the GUI. If no schedule is selected or the selection is removed, * @a schedule is identical to MyMoneySchedule(). This signal is used * by plugins to get information about changes. */ void scheduleSelected(const MyMoneySchedule& schedule); void startMatchTransaction(const MyMoneyTransaction& t); void cancelMatchTransaction(); public: bool isActionToggled(const eMenu::Action _a); static const QHash s_Actions; private: /// \internal d-pointer class. class Private; /* * Actually, one should write "Private * const d" but that confuses the KIDL * compiler in this context. It complains about the const keyword. So we leave * it out here */ /// \internal d-pointer instance. Private* d; }; extern KMyMoneyApp *kmymoney; class KMStatus { public: explicit KMStatus(const QString &text); ~KMStatus(); private: QString m_prevText; }; #define KMSTATUS(msg) KMStatus _thisStatus(msg) #endif // KMYMONEY_H diff --git a/kmymoney/mymoney/mymoneyfile.cpp b/kmymoney/mymoney/mymoneyfile.cpp index 5a3636579..05a40b3b1 100644 --- a/kmymoney/mymoney/mymoneyfile.cpp +++ b/kmymoney/mymoney/mymoneyfile.cpp @@ -1,3744 +1,3745 @@ /*************************************************************************** mymoneyfile.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2002, 2007-2011 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyfile.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes -#include "imymoneystorage.h" + +#include "mymoneystoragemgr.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneyaccountloan.h" #include "mymoneysecurity.h" #include "mymoneyreport.h" #include "mymoneybalancecache.h" #include "mymoneybudget.h" #include "mymoneyprice.h" #include "mymoneyobjectcontainer.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneycostcenter.h" #include "mymoneyexception.h" #include "onlinejob.h" #include "storageenums.h" #include "mymoneystoragenames.h" #include "mymoneyenums.h" // include the following line to get a 'cout' for debug purposes // #include using namespace eMyMoney; using namespace MyMoneyStandardAccounts; const QString MyMoneyFile::AccountSeparator = QChar(':'); MyMoneyFile MyMoneyFile::file; typedef QList > BalanceNotifyList; typedef QMap CacheNotifyList; /// @todo make this template based class MyMoneyNotification { public: MyMoneyNotification(File::Mode mode, const MyMoneyTransaction& t) : m_objType(File::Object::Transaction), m_notificationMode(mode), m_id(t.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyAccount& acc) : m_objType(File::Object::Account), m_notificationMode(mode), m_id(acc.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyInstitution& institution) : m_objType(File::Object::Institution), m_notificationMode(mode), m_id(institution.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyPayee& payee) : m_objType(File::Object::Payee), m_notificationMode(mode), m_id(payee.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneyTag& tag) : m_objType(File::Object::Tag), m_notificationMode(mode), m_id(tag.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneySchedule& schedule) : m_objType(File::Object::Schedule), m_notificationMode(mode), m_id(schedule.id()) { } MyMoneyNotification(File::Mode mode, const MyMoneySecurity& security) : m_objType(File::Object::Security), m_notificationMode(mode), m_id(security.id()) { } MyMoneyNotification(File::Mode mode, const onlineJob& job) : m_objType(File::Object::OnlineJob), m_notificationMode(mode), m_id(job.id()) { } File::Object objectType() const { return m_objType; } File::Mode notificationMode() const { return m_notificationMode; } const QString& id() const { return m_id; } protected: MyMoneyNotification(File::Object obj, File::Mode mode, const QString& id) : m_objType(obj), m_notificationMode(mode), m_id(id) {} private: File::Object m_objType; File::Mode m_notificationMode; QString m_id; }; class MyMoneyFile::Private { public: Private() : m_storage(0), m_inTransaction(false) {} ~Private() { delete m_storage; } /** * This method is used to add an id to the list of objects * to be removed from the cache. If id is empty, then nothing is added to the list. * * @param id id of object to be notified * @param reload reload the object (@c true) or not (@c false). The default is @c true * @see attach, detach */ void addCacheNotification(const QString& id, bool reload = true) { if (!id.isEmpty()) m_notificationList[id] = reload; } void addCacheNotification(const QString& id, const QDate& date, bool reload = true) { if (!id.isEmpty()) { m_notificationList[id] = reload; m_balanceNotifyList.append(std::make_pair(id, date)); } } /** * This method is used to clear the notification list */ void clearCacheNotification() { // reset list to be empty m_notificationList.clear(); m_balanceNotifyList.clear(); } /** * This method is used to clear all * objects mentioned in m_notificationList from the cache. */ void notify() { QMap::ConstIterator it = m_notificationList.constBegin(); while (it != m_notificationList.constEnd()) { if (*it) m_cache.refresh(it.key()); else m_cache.clear(it.key()); ++it; } foreach (const BalanceNotifyList::value_type & i, m_balanceNotifyList) { m_balanceChangedSet += i.first; if (i.second.isValid()) { m_balanceCache.clear(i.first, i.second); } else { m_balanceCache.clear(i.first); } } clearCacheNotification(); } /** * This method checks if a storage object is attached and * throws and exception if not. */ inline void checkStorage() const { if (m_storage == 0) throw MYMONEYEXCEPTION("No storage object attached to MyMoneyFile"); } /** * This method checks that a transaction has been started with * startTransaction() and throws an exception otherwise. Calls * checkStorage() to make sure a storage object is present and attached. */ void checkTransaction(const char* txt) const { checkStorage(); if (!m_inTransaction) { throw MYMONEYEXCEPTION(QString("No transaction started for %1").arg(txt)); } } void priceChanged(const MyMoneyFile& file, const MyMoneyPrice price) { // get all affected accounts and add them to the m_valueChangedSet QList accList; file.accountList(accList); QList::const_iterator account_it; for (account_it = accList.constBegin(); account_it != accList.constEnd(); ++account_it) { QString currencyId = account_it->currencyId(); if (currencyId != file.baseCurrency().id() && (currencyId == price.from() || currencyId == price.to())) { // this account is not in the base currency and the price affects it's value m_valueChangedSet.insert(account_it->id()); } } } /** * This member points to the storage strategy */ - IMyMoneyStorage *m_storage; + MyMoneyStorageMgr *m_storage; bool m_inTransaction; MyMoneySecurity m_baseCurrency; /** * @brief Cache for MyMoneyObjects * * It is also used to emit the objectAdded() and objectModified() signals. * => If one of these signals is used, you must use this cache. */ MyMoneyObjectContainer m_cache; MyMoneyPriceList m_priceCache; MyMoneyBalanceCache m_balanceCache; /** * This member keeps a list of ids to notify after a single * operation is completed. The boolean is used as follows * during processing of the list: * * false - don't reload the object immediately * true - reload the object immediately */ CacheNotifyList m_notificationList; /** * This member keeps a list of account ids to notify * after a single operation is completed. The balance cache * is cleared for that account and all dates on or after * the one supplied. If the date is invalid, the entire * balance cache is cleared for that account. */ BalanceNotifyList m_balanceNotifyList; /** * This member keeps a list of account ids for which * a balanceChanged() signal needs to be emitted when * a set of operations has been committed. * * @sa MyMoneyFile::commitTransaction() */ QSet m_balanceChangedSet; /** * This member keeps a list of account ids for which * a valueChanged() signal needs to be emitted when * a set of operations has been committed. * * @sa MyMoneyFile::commitTransaction() */ QSet m_valueChangedSet; /** * This member keeps the list of changes in the engine * in historical order. The type can be 'added', 'modified' * or removed. */ QList m_changeSet; }; class MyMoneyNotifier { public: MyMoneyNotifier(MyMoneyFile::Private* file) { m_file = file; m_file->clearCacheNotification(); }; ~MyMoneyNotifier() { m_file->notify(); }; private: MyMoneyFile::Private* m_file; }; MyMoneyFile::MyMoneyFile() : d(new Private) { } MyMoneyFile::~MyMoneyFile() { delete d; } -MyMoneyFile::MyMoneyFile(IMyMoneyStorage *storage) : +MyMoneyFile::MyMoneyFile(MyMoneyStorageMgr *storage) : d(new Private) { attachStorage(storage); } MyMoneyFile* MyMoneyFile::instance() { return &file; } -void MyMoneyFile::attachStorage(IMyMoneyStorage* const storage) +void MyMoneyFile::attachStorage(MyMoneyStorageMgr* const storage) { if (d->m_storage != 0) throw MYMONEYEXCEPTION("Storage already attached"); if (storage == 0) throw MYMONEYEXCEPTION("Storage must not be 0"); d->m_storage = storage; // force reload of base currency d->m_baseCurrency = MyMoneySecurity(); // and the whole cache d->m_balanceCache.clear(); d->m_cache.clear(storage); d->m_priceCache.clear(); preloadCache(); // notify application about new data availability emit beginChangeNotification(); emit dataChanged(); emit endChangeNotification(); } -void MyMoneyFile::detachStorage(IMyMoneyStorage* const /* storage */) +void MyMoneyFile::detachStorage(MyMoneyStorageMgr* const /* storage */) { d->m_balanceCache.clear(); d->m_cache.clear(); d->m_priceCache.clear(); d->m_storage = 0; } -IMyMoneyStorage* MyMoneyFile::storage() const +MyMoneyStorageMgr* MyMoneyFile::storage() const { return d->m_storage; } bool MyMoneyFile::storageAttached() const { return d->m_storage != 0; } void MyMoneyFile::startTransaction() { d->checkStorage(); if (d->m_inTransaction) { throw MYMONEYEXCEPTION("Already started a transaction!"); } d->m_storage->startTransaction(); d->m_inTransaction = true; d->m_changeSet.clear(); } bool MyMoneyFile::hasTransaction() const { return d->m_inTransaction; } void MyMoneyFile::commitTransaction() { d->checkTransaction(Q_FUNC_INFO); // commit the transaction in the storage bool changed = d->m_storage->commitTransaction(); d->m_inTransaction = false; // inform the outside world about the beginning of notifications emit beginChangeNotification(); // Now it's time to send out some signals to the outside world // First we go through the d->m_changeSet and emit respective // signals about addition, modification and removal of engine objects QList::const_iterator it = d->m_changeSet.constBegin(); while (it != d->m_changeSet.constEnd()) { if ((*it).notificationMode() == File::Mode::Remove) { emit objectRemoved((*it).objectType(), (*it).id()); // if there is a balance change recorded for this account remove it since the account itself will be removed // this can happen when deleting categories that have transactions and the reassign category feature was used d->m_balanceChangedSet.remove((*it).id()); } else { const MyMoneyObject * obj = 0; MyMoneyTransaction tr; switch((*it).objectType()) { case File::Object::Transaction: tr = transaction((*it).id()); obj = &tr; break; default: obj = d->m_cache.object((*it).id()); break; } if (obj) { if ((*it).notificationMode() == File::Mode::Add) { emit objectAdded((*it).objectType(), obj); } else { emit objectModified((*it).objectType(), obj); } } } ++it; } // we're done with the change set, so we clear it d->m_changeSet.clear(); // now send out the balanceChanged signal for all those // accounts for which we have an indication about a possible // change. foreach (const QString& id, d->m_balanceChangedSet) { // if we notify about balance change we don't need to notify about value change // for the same account since a balance change implies a value change d->m_valueChangedSet.remove(id); const auto acc = d->m_cache.account(id); emit balanceChanged(acc); } d->m_balanceChangedSet.clear(); // now notify about the remaining value changes foreach (const QString& id, d->m_valueChangedSet) { const auto acc = d->m_cache.account(id); emit valueChanged(acc); } d->m_valueChangedSet.clear(); // as a last action, send out the global dataChanged signal if (changed) { emit dataChanged(); } // inform the outside world about the end of notifications emit endChangeNotification(); } void MyMoneyFile::rollbackTransaction() { d->checkTransaction(Q_FUNC_INFO); d->m_storage->rollbackTransaction(); d->m_inTransaction = false; preloadCache(); d->m_balanceChangedSet.clear(); d->m_valueChangedSet.clear(); d->m_changeSet.clear(); } void MyMoneyFile::addInstitution(MyMoneyInstitution& institution) { // perform some checks to see that the institution stuff is OK. For // now we assume that the institution must have a name, the ID is not set // and it does not have a parent (MyMoneyFile). if (institution.name().length() == 0 || institution.id().length() != 0) throw MYMONEYEXCEPTION("Not a new institution"); d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addInstitution(institution); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Add, institution); } void MyMoneyFile::modifyInstitution(const MyMoneyInstitution& institution) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifyInstitution(institution); d->addCacheNotification(institution.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); } void MyMoneyFile::modifyTransaction(const MyMoneyTransaction& transaction) { d->checkTransaction(Q_FUNC_INFO); MyMoneyTransaction tCopy(transaction); // now check the splits bool loanAccountAffected = false; foreach (const auto split, transaction.splits()) { // the following line will throw an exception if the // account does not exist auto acc = MyMoneyFile::account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot store split with no account assigned"); if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION("Cannot store split referencing standard account"); if (acc.isLoan() && (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))) loanAccountAffected = true; } // change transfer splits between asset/liability and loan accounts // into amortization splits if (loanAccountAffected) { foreach (const auto split, transaction.splits()) { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) { auto acc = MyMoneyFile::account(split.accountId()); if (acc.isAssetLiability()) { MyMoneySplit s = split; s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); tCopy.modifySplit(s); } } } } // clear all changed objects from cache MyMoneyNotifier notifier(d); // get the current setting of this transaction MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id()); // scan the splits again to update notification list // and mark all accounts that are referenced foreach (const auto split, tr.splits()) { d->addCacheNotification(split.accountId(), tr.postDate()); d->addCacheNotification(split.payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification(split.tagList()); ?? } // make sure the value is rounded to the accounts precision fixSplitPrecision(tCopy); // perform modification d->m_storage->modifyTransaction(tCopy); // and mark all accounts that are referenced foreach (const auto split, tCopy.splits()) { d->addCacheNotification(split.accountId(), tCopy.postDate()); d->addCacheNotification(split.payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification(split.tagList()); ?? } d->m_changeSet += MyMoneyNotification(File::Mode::Modify, transaction); } void MyMoneyFile::modifyAccount(const MyMoneyAccount& _account) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount account(_account); auto acc = MyMoneyFile::account(account.id()); // check that for standard accounts only specific parameters are changed if (isStandardAccount(account.id())) { // make sure to use the stuff we found on file account = acc; // and only use the changes that are allowed account.setName(_account.name()); account.setCurrencyId(_account.currencyId()); // now check that it is the same if (!(account == _account)) throw MYMONEYEXCEPTION("Unable to modify the standard account groups"); } if (account.accountType() != acc.accountType() && !account.isLiquidAsset() && !acc.isLiquidAsset()) throw MYMONEYEXCEPTION("Unable to change account type"); // clear all changed objects from cache MyMoneyNotifier notifier(d); // if the account was moved to another institution, we notify // the old one as well as the new one and the structure change if (acc.institutionId() != account.institutionId()) { MyMoneyInstitution inst; if (!acc.institutionId().isEmpty()) { inst = institution(acc.institutionId()); inst.removeAccountId(acc.id()); modifyInstitution(inst); // modifyInstitution updates d->m_changeSet already } if (!account.institutionId().isEmpty()) { inst = institution(account.institutionId()); inst.addAccountId(acc.id()); modifyInstitution(inst); // modifyInstitution updates d->m_changeSet already } d->addCacheNotification(acc.institutionId()); d->addCacheNotification(account.institutionId()); } d->m_storage->modifyAccount(account); d->addCacheNotification(account.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, account); } void MyMoneyFile::reparentAccount(MyMoneyAccount &acc, MyMoneyAccount& parent) { d->checkTransaction(Q_FUNC_INFO); // check that it's not one of the standard account groups if (isStandardAccount(acc.id())) throw MYMONEYEXCEPTION("Unable to reparent the standard account groups"); if (acc.accountGroup() == parent.accountGroup() || (acc.accountType() == Account::Type::Income && parent.accountType() == Account::Type::Expense) || (acc.accountType() == Account::Type::Expense && parent.accountType() == Account::Type::Income)) { if (acc.isInvest() && parent.accountType() != Account::Type::Investment) throw MYMONEYEXCEPTION("Unable to reparent Stock to non-investment account"); if (parent.accountType() == Account::Type::Investment && !acc.isInvest()) throw MYMONEYEXCEPTION("Unable to reparent non-stock to investment account"); // clear all changed objects from cache MyMoneyNotifier notifier(d); // keep a notification of the current parent MyMoneyAccount curParent = account(acc.parentAccountId()); d->addCacheNotification(curParent.id()); d->m_storage->reparentAccount(acc, parent); // and also keep one for the account itself and the new parent d->addCacheNotification(acc.id()); d->addCacheNotification(parent.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, curParent); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } else throw MYMONEYEXCEPTION("Unable to reparent to different account type"); } MyMoneyInstitution MyMoneyFile::institution(const QString& id) const { return d->m_cache.institution(id); } MyMoneyAccount MyMoneyFile::account(const QString& id) const { return d->m_cache.account(id); } MyMoneyAccount MyMoneyFile::subAccountByName(const MyMoneyAccount& acc, const QString& name) const { static MyMoneyAccount nullAccount; foreach (const auto sAccount, acc.accountList()) { const auto sacc = account(sAccount); if (sacc.name() == name) return sacc; } return nullAccount; } MyMoneyAccount MyMoneyFile::accountByName(const QString& name) const { return d->m_cache.accountByName(name); } void MyMoneyFile::removeTransaction(const MyMoneyTransaction& transaction) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // get the engine's idea about this transaction MyMoneyTransaction tr = MyMoneyFile::transaction(transaction.id()); // scan the splits again to update notification list foreach (const auto split, tr.splits()) { auto acc = account(split.accountId()); if (acc.isClosed()) throw MYMONEYEXCEPTION(i18n("Cannot remove transaction that references a closed account.")); d->addCacheNotification(split.accountId(), tr.postDate()); d->addCacheNotification(split.payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification(split.tagList()); ?? } d->m_storage->removeTransaction(transaction); // remove a possible notification of that same object from the changeSet QList::iterator it; for(it = d->m_changeSet.begin(); it != d->m_changeSet.end();) { if((*it).id() == transaction.id()) { it = d->m_changeSet.erase(it); } else { ++it; } } d->m_changeSet += MyMoneyNotification(File::Mode::Remove, transaction); } bool MyMoneyFile::hasActiveSplits(const QString& id) const { d->checkStorage(); return d->m_storage->hasActiveSplits(id); } bool MyMoneyFile::isStandardAccount(const QString& id) const { d->checkStorage(); return d->m_storage->isStandardAccount(id); } void MyMoneyFile::setAccountName(const QString& id, const QString& name) const { d->checkTransaction(Q_FUNC_INFO); MyMoneyNotifier notifier(d); auto acc = account(id); d->m_storage->setAccountName(id, name); d->addCacheNotification(id); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } void MyMoneyFile::removeAccount(const MyMoneyAccount& account) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount parent; MyMoneyAccount acc; MyMoneyInstitution institution; // check that the account and its parent exist // this will throw an exception if the id is unknown acc = MyMoneyFile::account(account.id()); parent = MyMoneyFile::account(account.parentAccountId()); if (!acc.institutionId().isEmpty()) institution = MyMoneyFile::institution(acc.institutionId()); // check that it's not one of the standard account groups if (isStandardAccount(account.id())) throw MYMONEYEXCEPTION("Unable to remove the standard account groups"); if (hasActiveSplits(account.id())) { throw MYMONEYEXCEPTION("Unable to remove account with active splits"); } // clear all changed objects from cache MyMoneyNotifier notifier(d); // collect all sub-ordinate accounts for notification foreach (const QString& id, acc.accountList()) { d->addCacheNotification(id); const auto acc = MyMoneyFile::account(id); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } // don't forget the parent and a possible institution d->addCacheNotification(parent.id()); d->addCacheNotification(account.institutionId()); if (!institution.id().isEmpty()) { institution.removeAccountId(account.id()); d->m_storage->modifyInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); } acc.setInstitutionId(QString()); d->m_storage->removeAccount(acc); d->addCacheNotification(acc.id(), false); d->m_cache.clear(acc.id()); d->m_balanceCache.clear(acc.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, acc); } void MyMoneyFile::removeAccountList(const QStringList& account_list, unsigned int level) { if (level > 100) throw MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::removeAccountList]!"); d->checkTransaction(Q_FUNC_INFO); // upon entry, we check that we could proceed with the operation if (!level) { if (!hasOnlyUnusedAccounts(account_list, 0)) { throw MYMONEYEXCEPTION("One or more accounts cannot be removed"); } } // process all accounts in the list and test if they have transactions assigned foreach (const auto sAccount, account_list) { auto a = d->m_storage->account(sAccount); //qDebug() << "Deleting account '"<< a.name() << "'"; // first remove all sub-accounts if (!a.accountList().isEmpty()) { removeAccountList(a.accountList(), level + 1); // then remove account itself, but we first have to get // rid of the account list that is still stored in // the MyMoneyAccount object. Easiest way is to get a fresh copy. a = d->m_storage->account(sAccount); } // make sure to remove the item from the cache d->m_cache.clear(a.id()); removeAccount(a); } } bool MyMoneyFile::hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level) { if (level > 100) throw MYMONEYEXCEPTION("Too deep recursion in [MyMoneyFile::hasOnlyUnusedAccounts]!"); // process all accounts in the list and test if they have transactions assigned foreach (const auto sAccount, account_list) { if (transactionCount(sAccount) != 0) return false; // the current account has a transaction assigned if (!hasOnlyUnusedAccounts(account(sAccount).accountList(), level + 1)) return false; // some sub-account has a transaction assigned } return true; // all subaccounts unused } void MyMoneyFile::removeInstitution(const MyMoneyInstitution& institution) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); QList::ConstIterator it_a; MyMoneyInstitution inst = MyMoneyFile::institution(institution.id()); bool blocked = signalsBlocked(); blockSignals(true); for (it_a = inst.accountList().constBegin(); it_a != inst.accountList().constEnd(); ++it_a) { auto acc = account(*it_a); acc.setInstitutionId(QString()); modifyAccount(acc); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, acc); } blockSignals(blocked); d->m_storage->removeInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, institution); d->addCacheNotification(institution.id(), false); } void MyMoneyFile::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { // make sure we have a currency. If none is assigned, we assume base currency if (newAccount.currencyId().isEmpty()) newAccount.setCurrencyId(baseCurrency().id()); MyMoneyFileTransaction ft; try { int pos; // check for ':' in the name and use it as separator for a hierarchy while ((pos = newAccount.name().indexOf(MyMoneyFile::AccountSeparator)) != -1) { QString part = newAccount.name().left(pos); QString remainder = newAccount.name().mid(pos + 1); const MyMoneyAccount& existingAccount = subAccountByName(parentAccount, part); if (existingAccount.id().isEmpty()) { newAccount.setName(part); addAccount(newAccount, parentAccount); parentAccount = newAccount; } else { parentAccount = existingAccount; } newAccount.setParentAccountId(QString()); // make sure, there's no parent newAccount.clearId(); // and no id set for adding newAccount.removeAccountIds(); // and no sub-account ids newAccount.setName(remainder); } addAccount(newAccount, parentAccount); // in case of a loan account, we add the initial payment if ((newAccount.accountType() == Account::Type::Loan || newAccount.accountType() == Account::Type::AssetLoan) && !newAccount.value("kmm-loan-payment-acc").isEmpty() && !newAccount.value("kmm-loan-payment-date").isEmpty()) { MyMoneyAccountLoan acc(newAccount); MyMoneyTransaction t; MyMoneySplit a, b; a.setAccountId(acc.id()); b.setAccountId(acc.value("kmm-loan-payment-acc").toLatin1()); a.setValue(acc.loanAmount()); if (acc.accountType() == Account::Type::Loan) a.setValue(-a.value()); a.setShares(a.value()); b.setValue(-a.value()); b.setShares(b.value()); a.setMemo(i18n("Loan payout")); b.setMemo(i18n("Loan payout")); t.setPostDate(QDate::fromString(acc.value("kmm-loan-payment-date"), Qt::ISODate)); newAccount.deletePair("kmm-loan-payment-acc"); newAccount.deletePair("kmm-loan-payment-date"); MyMoneyFile::instance()->modifyAccount(newAccount); t.addSplit(a); t.addSplit(b); addTransaction(t); createOpeningBalanceTransaction(newAccount, openingBal); // in case of an investment account we check if we should create // a brokerage account } else if (newAccount.accountType() == Account::Type::Investment && !brokerageAccount.name().isEmpty()) { addAccount(brokerageAccount, parentAccount); // set a link from the investment account to the brokerage account modifyAccount(newAccount); createOpeningBalanceTransaction(brokerageAccount, openingBal); } else createOpeningBalanceTransaction(newAccount, openingBal); ft.commit(); } catch (const MyMoneyException &e) { qWarning("Unable to create account: %s", qPrintable(e.what())); throw MYMONEYEXCEPTION(e.what()); } } void MyMoneyFile::addAccount(MyMoneyAccount& account, MyMoneyAccount& parent) { d->checkTransaction(Q_FUNC_INFO); MyMoneyInstitution institution; // perform some checks to see that the account stuff is OK. For // now we assume that the account must have a name, has no // transaction and sub-accounts and parent account // it's own ID is not set and it does not have a pointer to (MyMoneyFile) if (account.name().length() == 0) throw MYMONEYEXCEPTION("Account has no name"); if (account.id().length() != 0) throw MYMONEYEXCEPTION("New account must have no id"); if (account.accountList().count() != 0) throw MYMONEYEXCEPTION("New account must have no sub-accounts"); if (!account.parentAccountId().isEmpty()) throw MYMONEYEXCEPTION("New account must have no parent-id"); if (account.accountType() == Account::Type::Unknown) throw MYMONEYEXCEPTION("Account has invalid type"); // make sure, that the parent account exists // if not, an exception is thrown. If it exists, // get a copy of the current data auto acc = MyMoneyFile::account(parent.id()); #if 0 // TODO: remove the following code as we now can have multiple accounts // with the same name even in the same hierarchy position of the account tree // // check if the selected name is currently not among the child accounts // if we find one, then return it as the new account QStringList::const_iterator it_a; foreach (const auto accountID, acc.accountList()) { MyMoneyAccount a = MyMoneyFile::account(accountID); if (account.name() == a.name()) { account = a; return; } } #endif // FIXME: make sure, that the parent has the same type // I left it out here because I don't know, if there is // a tight coupling between e.g. checking accounts and the // class asset. It certainly does not make sense to create an // expense account under an income account. Maybe it does, I don't know. // We enforce, that a stock account can never be a parent and // that the parent for a stock account must be an investment. Also, // an investment cannot have another investment account as it's parent if (parent.isInvest()) throw MYMONEYEXCEPTION("Stock account cannot be parent account"); if (account.isInvest() && parent.accountType() != Account::Type::Investment) throw MYMONEYEXCEPTION("Stock account must have investment account as parent "); if (!account.isInvest() && parent.accountType() == Account::Type::Investment) throw MYMONEYEXCEPTION("Investment account can only have stock accounts as children"); // clear all changed objects from cache MyMoneyNotifier notifier(d); // if an institution is set, verify that it exists if (account.institutionId().length() != 0) { // check the presence of the institution. if it // does not exist, an exception is thrown institution = MyMoneyFile::institution(account.institutionId()); } // if we don't have a valid opening date use today if (!account.openingDate().isValid()) { account.setOpeningDate(QDate::currentDate()); } // make sure to set the opening date for categories to a // fixed date (1900-1-1). See #313793 on b.k.o for details if (account.isIncomeExpense()) { account.setOpeningDate(QDate(1900, 1, 1)); } // if we don't have a currency assigned use the base currency if (account.currencyId().isEmpty()) { account.setCurrencyId(baseCurrency().id()); } // make sure the parent id is setup account.setParentAccountId(parent.id()); d->m_storage->addAccount(account); d->m_changeSet += MyMoneyNotification(File::Mode::Add, account); d->m_storage->addAccount(parent, account); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, parent); if (account.institutionId().length() != 0) { institution.addAccountId(account.id()); d->m_storage->modifyInstitution(institution); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, institution); d->addCacheNotification(institution.id()); } // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadAccount(account); d->addCacheNotification(parent.id()); } MyMoneyTransaction MyMoneyFile::createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance) { MyMoneyTransaction t; // if the opening balance is not zero, we need // to create the respective transaction if (!balance.isZero()) { d->checkTransaction(Q_FUNC_INFO); MyMoneySecurity currency = security(acc.currencyId()); MyMoneyAccount openAcc = openingBalanceAccount(currency); if (openAcc.openingDate() > acc.openingDate()) { openAcc.setOpeningDate(acc.openingDate()); modifyAccount(openAcc); } MyMoneySplit s; t.setPostDate(acc.openingDate()); t.setCommodity(acc.currencyId()); s.setAccountId(acc.id()); s.setShares(balance); s.setValue(balance); t.addSplit(s); s.clearId(); s.setAccountId(openAcc.id()); s.setShares(-balance); s.setValue(-balance); t.addSplit(s); addTransaction(t); } return t; } QString MyMoneyFile::openingBalanceTransaction(const MyMoneyAccount& acc) const { QString result; MyMoneySecurity currency = security(acc.currencyId()); MyMoneyAccount openAcc; try { openAcc = openingBalanceAccount(currency); } catch (const MyMoneyException &) { return result; } // Iterate over all the opening balance transactions for this currency MyMoneyTransactionFilter filter; filter.addAccount(openAcc.id()); QList transactions = transactionList(filter); QList::const_iterator it_t = transactions.constBegin(); while (it_t != transactions.constEnd()) { try { // Test whether the transaction also includes a split into // this account (*it_t).splitByAccount(acc.id(), true /*match*/); // If so, we have a winner! result = (*it_t).id(); break; } catch (const MyMoneyException &) { // If not, keep searching ++it_t; } } return result; } MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) { if (!security.isCurrency()) throw MYMONEYEXCEPTION("Opening balance for non currencies not supported"); try { return openingBalanceAccount_internal(security); } catch (const MyMoneyException &) { MyMoneyFileTransaction ft; MyMoneyAccount acc; try { acc = createOpeningBalanceAccount(security); ft.commit(); } catch (const MyMoneyException &) { qDebug("Unable to create opening balance account for security %s", qPrintable(security.id())); } return acc; } } MyMoneyAccount MyMoneyFile::openingBalanceAccount(const MyMoneySecurity& security) const { return openingBalanceAccount_internal(security); } MyMoneyAccount MyMoneyFile::openingBalanceAccount_internal(const MyMoneySecurity& security) const { if (!security.isCurrency()) throw MYMONEYEXCEPTION("Opening balance for non currencies not supported"); MyMoneyAccount acc; QList accounts; QList::ConstIterator it; accountList(accounts, equity().accountList(), true); for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { if (it->value("OpeningBalanceAccount") == QLatin1String("Yes") && it->currencyId() == security.id()) { acc = *it; break; } } if (acc.id().isEmpty()) { for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { if (it->name().startsWith(MyMoneyFile::openingBalancesPrefix()) && it->currencyId() == security.id()) { acc = *it; break; } } } if (acc.id().isEmpty()) { throw MYMONEYEXCEPTION(QString("No opening balance account for %1").arg(security.tradingSymbol())); } return acc; } MyMoneyAccount MyMoneyFile::createOpeningBalanceAccount(const MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount acc; QList accounts; QList::ConstIterator it; accountList(accounts, equity().accountList(), true); // find present opening balance accounts without containing '(' QString name; QString parentAccountId; QRegExp exp(QString("\\([A-Z]{3}\\)")); for (it = accounts.constBegin(); it != accounts.constEnd(); ++it) { if (it->value("OpeningBalanceAccount") == QLatin1String("Yes") && exp.indexIn(it->name()) == -1) { name = it->name(); parentAccountId = it->parentAccountId(); break; } } if (name.isEmpty()) name = MyMoneyFile::openingBalancesPrefix(); if (security.id() != baseCurrency().id()) { name += QString(" (%1)").arg(security.id()); } acc.setName(name); acc.setAccountType(Account::Type::Equity); acc.setCurrencyId(security.id()); acc.setValue("OpeningBalanceAccount", "Yes"); MyMoneyAccount parent = !parentAccountId.isEmpty() ? account(parentAccountId) : equity(); this->addAccount(acc, parent); return acc; } void MyMoneyFile::addTransaction(MyMoneyTransaction& transaction) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // perform some checks to see that the transaction stuff is OK. For // now we assume that // * no ids are assigned // * the date valid (must not be empty) // * the referenced accounts in the splits exist // first perform all the checks if (!transaction.id().isEmpty()) throw MYMONEYEXCEPTION("Unable to add transaction with id set"); if (!transaction.postDate().isValid()) throw MYMONEYEXCEPTION("Unable to add transaction with invalid postdate"); // now check the splits auto loanAccountAffected = false; foreach (const auto split, transaction.splits()) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts auto acc = MyMoneyFile::account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot add split with no account assigned"); if (acc.isLoan()) loanAccountAffected = true; if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION("Cannot add split referencing standard account"); } // change transfer splits between asset/liability and loan accounts // into amortization splits if (loanAccountAffected) { foreach (const auto split, transaction.splits()) { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) { auto acc = MyMoneyFile::account(split.accountId()); if (acc.isAssetLiability()) { MyMoneySplit s = split; s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization)); transaction.modifySplit(s); } } } } // check that we have a commodity if (transaction.commodity().isEmpty()) { transaction.setCommodity(baseCurrency().id()); } // make sure the value is rounded to the accounts precision fixSplitPrecision(transaction); // then add the transaction to the file global pool d->m_storage->addTransaction(transaction); // scan the splits again to update notification list foreach (const auto split, transaction.splits()) { d->addCacheNotification(split.accountId(), transaction.postDate()); d->addCacheNotification(split.payeeId()); //FIXME-ALEX Do I need to add d->addCacheNotification(split.tagList()); ?? } d->m_changeSet += MyMoneyNotification(File::Mode::Add, transaction); } MyMoneyTransaction MyMoneyFile::transaction(const QString& id) const { d->checkStorage(); return d->m_storage->transaction(id); } MyMoneyTransaction MyMoneyFile::transaction(const QString& account, const int idx) const { d->checkStorage(); return d->m_storage->transaction(account, idx); } void MyMoneyFile::addPayee(MyMoneyPayee& payee) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addPayee(payee); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadPayee(payee); d->m_changeSet += MyMoneyNotification(File::Mode::Add, payee); } MyMoneyPayee MyMoneyFile::payee(const QString& id) const { return d->m_cache.payee(id); } MyMoneyPayee MyMoneyFile::payeeByName(const QString& name) const { d->checkStorage(); return d->m_cache.payee(d->m_storage->payeeByName(name).id()); } void MyMoneyFile::modifyPayee(const MyMoneyPayee& payee) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->addCacheNotification(payee.id()); d->m_storage->modifyPayee(payee); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, payee); } void MyMoneyFile::removePayee(const MyMoneyPayee& payee) { d->checkTransaction(Q_FUNC_INFO); // FIXME we need to make sure, that the payee is not referenced anymore // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removePayee(payee); d->addCacheNotification(payee.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, payee); } void MyMoneyFile::addTag(MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addTag(tag); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadTag(tag); d->m_changeSet += MyMoneyNotification(File::Mode::Add, tag); } MyMoneyTag MyMoneyFile::tag(const QString& id) const { return d->m_cache.tag(id); } MyMoneyTag MyMoneyFile::tagByName(const QString& name) const { d->checkStorage(); return d->m_cache.tag(d->m_storage->tagByName(name).id()); } void MyMoneyFile::modifyTag(const MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->addCacheNotification(tag.id()); d->m_storage->modifyTag(tag); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, tag); } void MyMoneyFile::removeTag(const MyMoneyTag& tag) { d->checkTransaction(Q_FUNC_INFO); // FIXME we need to make sure, that the tag is not referenced anymore // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeTag(tag); d->addCacheNotification(tag.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, tag); } void MyMoneyFile::accountList(QList& list, const QStringList& idlist, const bool recursive) const { if (idlist.isEmpty()) { d->m_cache.account(list); #if 0 // TODO: I have no idea what this was good for, but it caused the networth report // to show double the numbers so I commented it out (ipwizard, 2008-05-24) if (d->m_storage && (list.isEmpty() || list.size() != d->m_storage->accountCount())) { d->m_storage->accountList(list); d->m_cache.preloadAccount(list); } #endif QList::Iterator it; for (it = list.begin(); it != list.end();) { if (isStandardAccount((*it).id())) { it = list.erase(it); } else { ++it; } } } else { QList::ConstIterator it; QList list_a; d->m_cache.account(list_a); for (it = list_a.constBegin(); it != list_a.constEnd(); ++it) { if (!isStandardAccount((*it).id())) { if (idlist.indexOf((*it).id()) != -1) { list.append(*it); if (recursive == true && !(*it).accountList().isEmpty()) { accountList(list, (*it).accountList(), true); } } } } } } void MyMoneyFile::institutionList(QList& list) const { d->m_cache.institution(list); } QList MyMoneyFile::institutionList() const { QList list; institutionList(list); return list; } // general get functions MyMoneyPayee MyMoneyFile::user() const { d->checkStorage(); return d->m_storage->user(); } // general set functions void MyMoneyFile::setUser(const MyMoneyPayee& user) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->setUser(user); } bool MyMoneyFile::dirty() const { if (!d->m_storage) return false; return d->m_storage->dirty(); } void MyMoneyFile::setDirty() const { d->checkStorage(); d->m_storage->setDirty(); } unsigned int MyMoneyFile::accountCount() const { d->checkStorage(); return d->m_storage->accountCount(); } void MyMoneyFile::ensureDefaultCurrency(MyMoneyAccount& acc) const { if (acc.currencyId().isEmpty()) { if (!baseCurrency().id().isEmpty()) acc.setCurrencyId(baseCurrency().id()); } } MyMoneyAccount MyMoneyFile::liability() const { d->checkStorage(); return d->m_cache.account(stdAccNames[stdAccLiability]); } MyMoneyAccount MyMoneyFile::asset() const { d->checkStorage(); return d->m_cache.account(stdAccNames[stdAccAsset]); } MyMoneyAccount MyMoneyFile::expense() const { d->checkStorage(); return d->m_cache.account(stdAccNames[stdAccExpense]); } MyMoneyAccount MyMoneyFile::income() const { d->checkStorage(); return d->m_cache.account(stdAccNames[stdAccIncome]); } MyMoneyAccount MyMoneyFile::equity() const { d->checkStorage(); return d->m_cache.account(stdAccNames[stdAccEquity]); } unsigned int MyMoneyFile::transactionCount(const QString& account) const { d->checkStorage(); return d->m_storage->transactionCount(account); } unsigned int MyMoneyFile::transactionCount() const { return transactionCount(QString()); } QMap MyMoneyFile::transactionCountMap() const { d->checkStorage(); return d->m_storage->transactionCountMap(); } unsigned int MyMoneyFile::institutionCount() const { d->checkStorage(); return d->m_storage->institutionCount(); } MyMoneyMoney MyMoneyFile::balance(const QString& id, const QDate& date) const { if (date.isValid()) { MyMoneyBalanceCacheItem bal = d->m_balanceCache.balance(id, date); if (bal.isValid()) return bal.balance(); } d->checkStorage(); MyMoneyMoney returnValue = d->m_storage->balance(id, date); if (date.isValid()) { d->m_balanceCache.insert(id, date, returnValue); } return returnValue; } MyMoneyMoney MyMoneyFile::balance(const QString& id) const { return balance(id, QDate()); } MyMoneyMoney MyMoneyFile::clearedBalance(const QString &id, const QDate& date) const { MyMoneyMoney cleared; QList list; cleared = balance(id, date); MyMoneyAccount account = this->account(id); MyMoneyMoney factor(1, 1); if (account.accountGroup() == Account::Type::Liability || account.accountGroup() == Account::Type::Equity) factor = -factor; MyMoneyTransactionFilter filter; filter.addAccount(id); filter.setDateFilter(QDate(), date); filter.setReportAllSplits(false); filter.addState((int)TransactionFilter::State::NotReconciled); transactionList(list, filter); for (QList::const_iterator it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { const QList& splits = (*it_t).splits(); for (QList::const_iterator it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { const MyMoneySplit &split = (*it_s); if (split.accountId() != id) continue; cleared -= split.shares(); } } return cleared * factor; } MyMoneyMoney MyMoneyFile::totalBalance(const QString& id, const QDate& date) const { d->checkStorage(); return d->m_storage->totalBalance(id, date); } MyMoneyMoney MyMoneyFile::totalBalance(const QString& id) const { return totalBalance(id, QDate()); } void MyMoneyFile::warningMissingRate(const QString& fromId, const QString& toId) const { MyMoneySecurity from, to; try { from = security(fromId); to = security(toId); qWarning("Missing price info for conversion from %s to %s", qPrintable(from.name()), qPrintable(to.name())); } catch (const MyMoneyException &e) { qWarning("Missing security caught in MyMoneyFile::warningMissingRate(): %s(%ld) %s", qPrintable(e.file()), e.line(), qPrintable(e.what())); } } void MyMoneyFile::transactionList(QList >& list, MyMoneyTransactionFilter& filter) const { d->checkStorage(); d->m_storage->transactionList(list, filter); } void MyMoneyFile::transactionList(QList& list, MyMoneyTransactionFilter& filter) const { d->checkStorage(); d->m_storage->transactionList(list, filter); } QList MyMoneyFile::transactionList(MyMoneyTransactionFilter& filter) const { QList list; transactionList(list, filter); return list; } QList MyMoneyFile::payeeList() const { QList list; d->m_cache.payee(list); return list; } QList MyMoneyFile::tagList() const { QList list; d->m_cache.tag(list); return list; } QString MyMoneyFile::accountToCategory(const QString& accountId, bool includeStandardAccounts) const { MyMoneyAccount acc; QString rc; if (!accountId.isEmpty()) { acc = account(accountId); do { if (!rc.isEmpty()) rc = AccountSeparator + rc; rc = acc.name() + rc; acc = account(acc.parentAccountId()); } while (!acc.id().isEmpty() && (includeStandardAccounts || !isStandardAccount(acc.id()))); } return rc; } QString MyMoneyFile::categoryToAccount(const QString& category, Account::Type type) const { QString id; // search the category in the expense accounts and if it is not found, try // to locate it in the income accounts if (type == Account::Type::Unknown || type == Account::Type::Expense) { id = locateSubAccount(MyMoneyFile::instance()->expense(), category); } if ((id.isEmpty() && type == Account::Type::Unknown) || type == Account::Type::Income) { id = locateSubAccount(MyMoneyFile::instance()->income(), category); } return id; } QString MyMoneyFile::categoryToAccount(const QString& category) const { return categoryToAccount(category, Account::Type::Unknown); } QString MyMoneyFile::nameToAccount(const QString& name) const { QString id; // search the category in the asset accounts and if it is not found, try // to locate it in the liability accounts id = locateSubAccount(MyMoneyFile::instance()->asset(), name); if (id.isEmpty()) id = locateSubAccount(MyMoneyFile::instance()->liability(), name); return id; } QString MyMoneyFile::parentName(const QString& name) const { return name.section(AccountSeparator, 0, -2); } QString MyMoneyFile::locateSubAccount(const MyMoneyAccount& base, const QString& category) const { MyMoneyAccount nextBase; QString level, remainder; level = category.section(AccountSeparator, 0, 0); remainder = category.section(AccountSeparator, 1); foreach (const auto sAccount, base.accountList()) { nextBase = account(sAccount); if (nextBase.name() == level) { if (remainder.isEmpty()) { return nextBase.id(); } return locateSubAccount(nextBase, remainder); } } return QString(); } QString MyMoneyFile::value(const QString& key) const { d->checkStorage(); return d->m_storage->value(key); } void MyMoneyFile::setValue(const QString& key, const QString& val) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->setValue(key, val); } void MyMoneyFile::deletePair(const QString& key) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->deletePair(key); } void MyMoneyFile::addSchedule(MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); foreach (const auto split, sched.transaction().splits()) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts auto acc = MyMoneyFile::account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot add split with no account assigned"); if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION("Cannot add split referencing standard account"); } // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addSchedule(sched); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadSchedule(sched); d->m_changeSet += MyMoneyNotification(File::Mode::Add, sched); } void MyMoneyFile::modifySchedule(const MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); foreach (const auto split, sched.transaction().splits()) { // the following line will throw an exception if the // account does not exist or is one of the standard accounts auto acc = MyMoneyFile::account(split.accountId()); if (acc.id().isEmpty()) throw MYMONEYEXCEPTION("Cannot store split with no account assigned"); if (isStandardAccount(split.accountId())) throw MYMONEYEXCEPTION("Cannot store split referencing standard account"); } // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifySchedule(sched); d->addCacheNotification(sched.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, sched); } void MyMoneyFile::removeSchedule(const MyMoneySchedule& sched) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeSchedule(sched); d->addCacheNotification(sched.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, sched); } MyMoneySchedule MyMoneyFile::schedule(const QString& id) const { return d->m_cache.schedule(id); } QList MyMoneyFile::scheduleList( const QString& accountId, const Schedule::Type type, const Schedule::Occurrence occurrence, const Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, const bool overdue) const { d->checkStorage(); return d->m_storage->scheduleList(accountId, type, occurrence, paymentType, startDate, endDate, overdue); } QList MyMoneyFile::scheduleList( const QString& accountId) const { return scheduleList(accountId, Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); } QList MyMoneyFile::scheduleList() const { return scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); } QStringList MyMoneyFile::consistencyCheck() { QList list; QList::Iterator it_a; QList::Iterator it_sch; QList::Iterator it_p; QList::Iterator it_t; QList::Iterator it_r; QStringList accountRebuild; QMap interestAccounts; MyMoneyAccount parent; MyMoneyAccount child; MyMoneyAccount toplevel; QString parentId; QStringList rc; int problemCount = 0; int unfixedCount = 0; QString problemAccount; // check that we have a storage object d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // get the current list of accounts accountList(list); // add the standard accounts list << MyMoneyFile::instance()->asset(); list << MyMoneyFile::instance()->liability(); list << MyMoneyFile::instance()->income(); list << MyMoneyFile::instance()->expense(); for (it_a = list.begin(); it_a != list.end(); ++it_a) { // no more checks for standard accounts if (isStandardAccount((*it_a).id())) { continue; } switch ((*it_a).accountGroup()) { case Account::Type::Asset: toplevel = asset(); break; case Account::Type::Liability: toplevel = liability(); break; case Account::Type::Expense: toplevel = expense(); break; case Account::Type::Income: toplevel = income(); break; case Account::Type::Equity: toplevel = equity(); break; default: qWarning("%s:%d This should never happen!", __FILE__ , __LINE__); break; } // check for loops in the hierarchy parentId = (*it_a).parentAccountId(); try { bool dropOut = false; while (!isStandardAccount(parentId) && !dropOut) { parent = account(parentId); if (parent.id() == (*it_a).id()) { // parent loops, so we need to re-parent to toplevel account // find parent account in our list problemCount++; QList::Iterator it_b; for (it_b = list.begin(); it_b != list.end(); ++it_b) { if ((*it_b).id() == parent.id()) { if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); rc << i18n(" * Loop detected between this account and account '%1'.", (*it_b).name()); rc << i18n(" Reparenting account '%2' to top level account '%1'.", toplevel.name(), (*it_a).name()); (*it_a).setParentAccountId(toplevel.id()); if (accountRebuild.contains(toplevel.id()) == 0) accountRebuild << toplevel.id(); if (accountRebuild.contains((*it_a).id()) == 0) accountRebuild << (*it_a).id(); dropOut = true; break; } } } } parentId = parent.parentAccountId(); } } catch (const MyMoneyException &) { // if we don't know about a parent, we catch it later } // check that the parent exists parentId = (*it_a).parentAccountId(); try { parent = account(parentId); if ((*it_a).accountGroup() != parent.accountGroup()) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } // the parent belongs to a different group, so we reconnect to the // master group account (asset, liability, etc) to which this account // should belong and update it in the engine. rc << i18n(" * Parent account '%1' belongs to a different group.", parent.name()); rc << i18n(" New parent account is the top level account '%1'.", toplevel.name()); (*it_a).setParentAccountId(toplevel.id()); // make sure to rebuild the sub-accounts of the top account // and the one we removed this account from if (accountRebuild.contains(toplevel.id()) == 0) accountRebuild << toplevel.id(); if (accountRebuild.contains(parent.id()) == 0) accountRebuild << parent.id(); } else if (!parent.accountList().contains((*it_a).id())) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } // parent exists, but does not have a reference to the account rc << i18n(" * Parent account '%1' does not contain '%2' as sub-account.", parent.name(), problemAccount); if (accountRebuild.contains(parent.id()) == 0) accountRebuild << parent.id(); } } catch (const MyMoneyException &) { // apparently, the parent does not exist anymore. we reconnect to the // master group account (asset, liability, etc) to which this account // should belong and update it in the engine. problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * The parent with id %1 does not exist anymore.", parentId); rc << i18n(" New parent account is the top level account '%1'.", toplevel.name()); (*it_a).setParentAccountId(toplevel.id()); d->addCacheNotification((*it_a).id()); // make sure to rebuild the sub-accounts of the top account if (accountRebuild.contains(toplevel.id()) == 0) accountRebuild << toplevel.id(); } // now check that all the children exist and have the correct type foreach (const auto accountID, (*it_a).accountList()) { // check that the child exists try { child = account(accountID); if (child.parentAccountId() != (*it_a).id()) { throw MYMONEYEXCEPTION("Child account has a different parent"); } } catch (const MyMoneyException &) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * Child account with id %1 does not exist anymore.", accountID); rc << i18n(" The child account list will be reconstructed."); if (accountRebuild.contains((*it_a).id()) == 0) accountRebuild << (*it_a).id(); } } // see if it is a loan account. if so, remember the assigned interest account if ((*it_a).isLoan()) { MyMoneyAccountLoan loan(*it_a); if (!loan.interestAccountId().isEmpty()) { interestAccounts[loan.interestAccountId()] = true; } try { payee(loan.payee()); } catch (const MyMoneyException &) { problemCount++; if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * The payee with id %1 referenced by the loan does not exist anymore.", loan.payee()); rc << i18n(" The payee will be removed."); // remove the payee - the account will be modified in the engine later (*it_a).deletePair("payee"); } } // check if it is a category and set the date to 1900-01-01 if different if ((*it_a).isIncomeExpense()) { if (((*it_a).openingDate().isValid() == false) || ((*it_a).openingDate() != QDate(1900, 1, 1))) { (*it_a).setOpeningDate(QDate(1900, 1, 1)); } } // check for clear text online password in the online settings if (!(*it_a).onlineBankingSettings().value("password").isEmpty()) { if (problemAccount != (*it_a).name()) { problemAccount = (*it_a).name(); rc << i18n("* Problem with account '%1'", problemAccount); } rc << i18n(" * Older versions of KMyMoney stored an OFX password for this account in cleartext."); rc << i18n(" Please open it in the account editor (Account/Edit account) once and press OK."); rc << i18n(" This will store the password in the KDE wallet and remove the cleartext version."); ++unfixedCount; } // if the account was modified, we need to update it in the engine if (!(d->m_storage->account((*it_a).id()) == (*it_a))) { try { d->m_storage->modifyAccount(*it_a, true); d->addCacheNotification((*it_a).id()); } catch (const MyMoneyException &) { rc << i18n(" * Unable to update account data in engine."); return rc; } } } if (accountRebuild.count() != 0) { rc << i18n("* Reconstructing the child lists for"); } // clear the affected lists for (it_a = list.begin(); it_a != list.end(); ++it_a) { if (accountRebuild.contains((*it_a).id())) { rc << QString(" %1").arg((*it_a).name()); // clear the account list (*it_a).removeAccountIds(); } } // reconstruct the lists for (it_a = list.begin(); it_a != list.end(); ++it_a) { QList::Iterator it; parentId = (*it_a).parentAccountId(); if (accountRebuild.contains(parentId)) { for (it = list.begin(); it != list.end(); ++it) { if ((*it).id() == parentId) { (*it).addAccountId((*it_a).id()); break; } } } } // update the engine objects for (it_a = list.begin(); it_a != list.end(); ++it_a) { if (accountRebuild.contains((*it_a).id())) { try { d->m_storage->modifyAccount(*it_a, true); d->addCacheNotification((*it_a).id()); } catch (const MyMoneyException &) { rc << i18n(" * Unable to update account data for account %1 in engine", (*it_a).name()); } } } // For some reason, files exist with invalid ids. This has been found in the payee id // so we fix them here QList pList = payeeList(); QMappayeeConversionMap; for (it_p = pList.begin(); it_p != pList.end(); ++it_p) { if ((*it_p).id().length() > 7) { // found one of those with an invalid ids // create a new one and store it in the map. MyMoneyPayee payee = (*it_p); payee.clearId(); d->m_storage->addPayee(payee); payeeConversionMap[(*it_p).id()] = payee.id(); rc << i18n(" * Payee %1 recreated with fixed id", payee.name()); ++problemCount; } } // Fix the transactions QList tList; MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); d->m_storage->transactionList(tList, filter); // Generate the list of interest accounts foreach (const auto transaction, tList) { foreach (const auto split, transaction.splits()) { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) interestAccounts[split.accountId()] = true; } } QSet supportedAccountTypes; supportedAccountTypes << Account::Type::Checkings << Account::Type::Savings << Account::Type::Cash << Account::Type::CreditCard << Account::Type::Asset << Account::Type::Liability; QSet reportedUnsupportedAccounts; for (it_t = tList.begin(); it_t != tList.end(); ++it_t) { MyMoneyTransaction t = (*it_t); bool tChanged = false; QDate accountOpeningDate; QStringList accountList; foreach (const auto split, t.splits()) { bool sChanged = false; MyMoneySplit s = split; if (payeeConversionMap.find(split.payeeId()) != payeeConversionMap.end()) { s.setPayeeId(payeeConversionMap[s.payeeId()]); sChanged = true; rc << i18n(" * Payee id updated in split of transaction '%1'.", t.id()); ++problemCount; } try { const auto acc = this->account(s.accountId()); // compute the newest opening date of all accounts involved in the transaction // in case the newest opening date is newer than the transaction post date, do one // of the following: // // a) for category and stock accounts: update the opening date of the account // b) for account types where the user cannot modify the opening date through // the UI issue a warning (for each account only once) // c) others will be caught later if (!acc.isIncomeExpense() && !acc.isInvest()) { if (acc.openingDate() > t.postDate()) { if (!accountOpeningDate.isValid() || acc.openingDate() > accountOpeningDate) { accountOpeningDate = acc.openingDate(); } accountList << this->accountToCategory(acc.id()); if (!supportedAccountTypes.contains(acc.accountType()) && !reportedUnsupportedAccounts.contains(acc.id())) { rc << i18n(" * Opening date of Account '%1' cannot be changed to support transaction '%2' post date.", this->accountToCategory(acc.id()), t.id()); reportedUnsupportedAccounts << acc.id(); ++unfixedCount; } } } else { if (acc.openingDate() > t.postDate()) { rc << i18n(" * Transaction '%1' post date '%2' is older than opening date '%4' of account '%3'.", t.id(), t.postDate().toString(Qt::ISODate), this->accountToCategory(acc.id()), acc.openingDate().toString(Qt::ISODate)); rc << i18n(" Account opening date updated."); MyMoneyAccount newAcc = acc; newAcc.setOpeningDate(t.postDate()); this->modifyAccount(newAcc); ++problemCount; } } // make sure, that shares and value have the same number if they // represent the same currency. if (t.commodity() == acc.currencyId() && s.shares().reduce() != s.value().reduce()) { // use the value as master if the transaction is balanced if (t.splitSum().isZero()) { s.setShares(s.value()); rc << i18n(" * shares set to value in split of transaction '%1'.", t.id()); } else { s.setValue(s.shares()); rc << i18n(" * value set to shares in split of transaction '%1'.", t.id()); } sChanged = true; ++problemCount; } } catch (const MyMoneyException &) { rc << i18n(" * Split %2 in transaction '%1' contains a reference to invalid account %3. Please fix manually.", t.id(), split.id(), split.accountId()); ++unfixedCount; } // make sure the interest splits are marked correct as such if (interestAccounts.find(s.accountId()) != interestAccounts.end() && s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { s.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); sChanged = true; rc << i18n(" * action marked as interest in split of transaction '%1'.", t.id()); ++problemCount; } if (sChanged) { tChanged = true; t.modifySplit(s); } } // make sure that the transaction's post date is valid if (!t.postDate().isValid()) { tChanged = true; t.setPostDate(t.entryDate().isValid() ? t.entryDate() : QDate::currentDate()); rc << i18n(" * Transaction '%1' has an invalid post date.", t.id()); rc << i18n(" The post date was updated to '%1'.", QLocale().toString(t.postDate(), QLocale::ShortFormat)); ++problemCount; } // check if the transaction's post date is after the opening date // of all accounts involved in the transaction. In case it is not, // issue a warning with the details about the transaction incl. // the account names and dates involved if (accountOpeningDate.isValid() && t.postDate() < accountOpeningDate) { QDate originalPostDate = t.postDate(); #if 0 // for now we do not activate the logic to move the post date to a later // point in time. This could cause some severe trouble if you have lots // of ancient data collected with older versions of KMyMoney that did not // enforce certain conditions like we do now. t.setPostDate(accountOpeningDate); tChanged = true; // copy the price information for investments to the new date QList::const_iterator it_t; foreach (const auto split, t.splits()) { if ((split.action() != "Buy") && (split.action() != "Reinvest")) { continue; } QString id = split.accountId(); auto acc = this->account(id); MyMoneySecurity sec = this->security(acc.currencyId()); MyMoneyPrice price(acc.currencyId(), sec.tradingCurrency(), t.postDate(), split.price(), "Transaction"); this->addPrice(price); break; } #endif 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)); rc << i18n(" Referenced accounts: %1", accountList.join(",")); rc << i18n(" The post date was not updated to '%1'.", QLocale().toString(accountOpeningDate, QLocale::ShortFormat)); ++unfixedCount; } if (tChanged) { d->m_storage->modifyTransaction(t); } } // Fix the schedules QList schList = scheduleList(); for (it_sch = schList.begin(); it_sch != schList.end(); ++it_sch) { MyMoneySchedule sch = (*it_sch); MyMoneyTransaction t = sch.transaction(); auto tChanged = false; foreach (const auto split, t.splits()) { MyMoneySplit s = split; bool sChanged = false; if (payeeConversionMap.find(split.payeeId()) != payeeConversionMap.end()) { s.setPayeeId(payeeConversionMap[s.payeeId()]); sChanged = true; rc << i18n(" * Payee id updated in split of schedule '%1'.", (*it_sch).name()); ++problemCount; } if (!split.value().isZero() && split.shares().isZero()) { s.setShares(s.value()); sChanged = true; rc << i18n(" * Split in scheduled transaction '%1' contained value != 0 and shares == 0.", (*it_sch).name()); rc << i18n(" Shares set to value."); ++problemCount; } // make sure, we don't have a bankid stored with a split in a schedule if (!split.bankID().isEmpty()) { s.setBankID(QString()); sChanged = true; rc << i18n(" * Removed bankid from split in scheduled transaction '%1'.", (*it_sch).name()); ++problemCount; } // make sure, that shares and value have the same number if they // represent the same currency. try { const auto acc = this->account(s.accountId()); if (t.commodity() == acc.currencyId() && s.shares().reduce() != s.value().reduce()) { // use the value as master if the transaction is balanced if (t.splitSum().isZero()) { s.setShares(s.value()); rc << i18n(" * shares set to value in split in schedule '%1'.", (*it_sch).name()); } else { s.setValue(s.shares()); rc << i18n(" * value set to shares in split in schedule '%1'.", (*it_sch).name()); } sChanged = true; ++problemCount; } } catch (const MyMoneyException &) { rc << i18n(" * Split %2 in schedule '%1' contains a reference to invalid account %3. Please fix manually.", (*it_sch).name(), split.id(), split.accountId()); ++unfixedCount; } if (sChanged) { t.modifySplit(s); tChanged = true; } } if (tChanged) { sch.setTransaction(t); d->m_storage->modifySchedule(sch); } } // Fix the reports QList rList = reportList(); for (it_r = rList.begin(); it_r != rList.end(); ++it_r) { MyMoneyReport r = *it_r; QStringList pList; QStringList::Iterator it_p; (*it_r).payees(pList); bool rChanged = false; for (it_p = pList.begin(); it_p != pList.end(); ++it_p) { if (payeeConversionMap.find(*it_p) != payeeConversionMap.end()) { rc << i18n(" * Payee id updated in report '%1'.", (*it_r).name()); ++problemCount; r.removeReference(*it_p); r.addPayee(payeeConversionMap[*it_p]); rChanged = true; } } if (rChanged) { d->m_storage->modifyReport(r); } } // erase old payee ids QMap::Iterator it_m; for (it_m = payeeConversionMap.begin(); it_m != payeeConversionMap.end(); ++it_m) { MyMoneyPayee payee = this->payee(it_m.key()); removePayee(payee); rc << i18n(" * Payee '%1' removed.", payee.id()); ++problemCount; } //look for accounts which have currencies other than the base currency but no price on the opening date //all accounts using base currency are excluded, since that's the base used for foreing currency calculation //thus it is considered as always present //accounts that represent Income/Expense categories are also excluded as price is irrelevant for their //fake opening date since a forex rate is required for all multi-currency transactions //get all currencies in use QStringList currencyList; QList accountForeignCurrency; QList accList; accountList(accList); QList::const_iterator account_it; for (account_it = accList.constBegin(); account_it != accList.constEnd(); ++account_it) { MyMoneyAccount account = *account_it; if (!account.isIncomeExpense() && !currencyList.contains(account.currencyId()) && account.currencyId() != baseCurrency().id() && !account.currencyId().isEmpty()) { //add the currency and the account-currency pair currencyList.append(account.currencyId()); accountForeignCurrency.append(account); } } MyMoneyPriceList pricesList = priceList(); QMap securityPriceDate; //get the first date of the price for each security MyMoneyPriceList::const_iterator prices_it; for (prices_it = pricesList.constBegin(); prices_it != pricesList.constEnd(); ++prices_it) { MyMoneyPrice firstPrice = (*((*prices_it).constBegin())); //only check the price if the currency is in use if (currencyList.contains(firstPrice.from()) || currencyList.contains(firstPrice.to())) { //check the security in the from field //if it is there, check if it is older QPair pricePair = qMakePair(firstPrice.from(), firstPrice.to()); securityPriceDate[pricePair] = firstPrice.date(); } } //compare the dates with the opening dates of the accounts using each currency QList::const_iterator accForeignList_it; bool firstInvProblem = true; for (accForeignList_it = accountForeignCurrency.constBegin(); accForeignList_it != accountForeignCurrency.constEnd(); ++accForeignList_it) { //setup the price pair correctly QPair pricePair; //setup the reverse, which can also be used for rate conversion QPair reversePricePair; if ((*accForeignList_it).isInvest()) { //if it is a stock, we have to search for a price from its stock to the currency of the account QString securityId = (*accForeignList_it).currencyId(); QString tradingCurrencyId = security(securityId).tradingCurrency(); pricePair = qMakePair(securityId, tradingCurrencyId); reversePricePair = qMakePair(tradingCurrencyId, securityId); } else { //if it is a regular account we search for a price from the currency of the account to the base currency QString currency = (*accForeignList_it).currencyId(); QString baseCurrencyId = baseCurrency().id(); pricePair = qMakePair(currency, baseCurrencyId); reversePricePair = qMakePair(baseCurrencyId, currency); } //compare the first price with the opening date of the account if ((!securityPriceDate.contains(pricePair) || securityPriceDate.value(pricePair) > (*accForeignList_it).openingDate()) && (!securityPriceDate.contains(reversePricePair) || securityPriceDate.value(reversePricePair) > (*accForeignList_it).openingDate())) { if (firstInvProblem) { firstInvProblem = false; rc << i18n("* Potential problem with investments/currencies"); } QDate openingDate = (*accForeignList_it).openingDate(); MyMoneySecurity secError = security((*accForeignList_it).currencyId()); if (!(*accForeignList_it).isInvest()) { 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)); rc << i18n(" Please enter a price for the currency on or before the opening date."); } else { rc << i18n(" * The investment '%1' has no price set for the opening date '%2'.", (*accForeignList_it).name(), openingDate.toString(Qt::ISODate)); rc << i18n(" Please enter a price for the investment on or before the opening date."); } ++unfixedCount; } } // Fix the budgets that somehow still reference invalid accounts QString problemBudget; QList bList = budgetList(); for (QList::const_iterator it_b = bList.constBegin(); it_b != bList.constEnd(); ++it_b) { MyMoneyBudget b = *it_b; QList baccounts = b.getaccounts(); bool bChanged = false; for (QList::const_iterator it_bacc = baccounts.constBegin(); it_bacc != baccounts.constEnd(); ++it_bacc) { try { account((*it_bacc).id()); } catch (const MyMoneyException &) { problemCount++; if (problemBudget != b.name()) { problemBudget = b.name(); rc << i18n("* Problem with budget '%1'", problemBudget); } rc << i18n(" * The account with id %1 referenced by the budget does not exist anymore.", (*it_bacc).id()); rc << i18n(" The account reference will be removed."); // remove the reference to the account b.removeReference((*it_bacc).id()); bChanged = true; } } if (bChanged) { d->m_storage->modifyBudget(b); } } // add more checks here if (problemCount == 0 && unfixedCount == 0) { rc << i18n("Finished: data is consistent."); } else { const QString problemsCorrected = i18np("%1 problem corrected.", "%1 problems corrected.", problemCount); const QString problemsRemaining = i18np("%1 problem still present.", "%1 problems still present.", unfixedCount); rc << QString(); 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); } return rc; } QString MyMoneyFile::createCategory(const MyMoneyAccount& base, const QString& name) { d->checkTransaction(Q_FUNC_INFO); MyMoneyAccount parent = base; QString categoryText; if (base.id() != expense().id() && base.id() != income().id()) throw MYMONEYEXCEPTION("Invalid base category"); QStringList subAccounts = name.split(AccountSeparator); QStringList::Iterator it; for (it = subAccounts.begin(); it != subAccounts.end(); ++it) { MyMoneyAccount categoryAccount; categoryAccount.setName(*it); categoryAccount.setAccountType(base.accountType()); if (it == subAccounts.begin()) categoryText += *it; else categoryText += (AccountSeparator + *it); // Only create the account if it doesn't exist try { QString categoryId = categoryToAccount(categoryText); if (categoryId.isEmpty()) addAccount(categoryAccount, parent); else { categoryAccount = account(categoryId); } } catch (const MyMoneyException &e) { qDebug("Unable to add account %s, %s, %s: %s", qPrintable(categoryAccount.name()), qPrintable(parent.name()), qPrintable(categoryText), qPrintable(e.what())); } parent = categoryAccount; } return categoryToAccount(name); } QString MyMoneyFile::checkCategory(const QString& name, const MyMoneyMoney& value, const MyMoneyMoney& value2) { QString accountId; MyMoneyAccount newAccount; bool found = true; if (!name.isEmpty()) { // The category might be constructed with an arbitraty depth (number of // colon delimited fields). We try to find a parent account within this // hierarchy by searching the following sequence: // // aaaa:bbbb:cccc:ddddd // // 1. search aaaa:bbbb:cccc:dddd, create nothing // 2. search aaaa:bbbb:cccc , create dddd // 3. search aaaa:bbbb , create cccc:dddd // 4. search aaaa , create bbbb:cccc:dddd // 5. don't search , create aaaa:bbbb:cccc:dddd newAccount.setName(name); QString accName; // part to be created (right side in above list) QString parent(name); // a possible parent part (left side in above list) do { accountId = categoryToAccount(parent); if (accountId.isEmpty()) { found = false; // prepare next step if (!accName.isEmpty()) accName.prepend(':'); accName.prepend(parent.section(':', -1)); newAccount.setName(accName); parent = parent.section(':', 0, -2); } else if (!accName.isEmpty()) { newAccount.setParentAccountId(accountId); } } while (!parent.isEmpty() && accountId.isEmpty()); // if we did not find the category, we create it if (!found) { MyMoneyAccount parent; if (newAccount.parentAccountId().isEmpty()) { if (!value.isNegative() && value2.isNegative()) parent = income(); else parent = expense(); } else { parent = account(newAccount.parentAccountId()); } newAccount.setAccountType((!value.isNegative() && value2.isNegative()) ? Account::Type::Income : Account::Type::Expense); MyMoneyAccount brokerage; // clear out the parent id, because createAccount() does not like that newAccount.setParentAccountId(QString()); createAccount(newAccount, parent, brokerage, MyMoneyMoney()); accountId = newAccount.id(); } } return accountId; } void MyMoneyFile::addSecurity(MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addSecurity(security); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadSecurity(security); d->m_changeSet += MyMoneyNotification(File::Mode::Add, security); } void MyMoneyFile::modifySecurity(const MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifySecurity(security); d->addCacheNotification(security.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, security); } void MyMoneyFile::removeSecurity(const MyMoneySecurity& security) { d->checkTransaction(Q_FUNC_INFO); // FIXME check that security is not referenced by other object // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeSecurity(security); d->addCacheNotification(security.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, security); } MyMoneySecurity MyMoneyFile::security(const QString& id) const { if (id.isEmpty()) return baseCurrency(); return d->m_cache.security(id); } QList MyMoneyFile::securityList() const { d->checkStorage(); return d->m_storage->securityList(); } void MyMoneyFile::addCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addCurrency(currency); // The notifier mechanism only refreshes the cache but does not // load new objects. So we simply force loading of the new one here d->m_cache.preloadSecurity(currency); d->m_changeSet += MyMoneyNotification(File::Mode::Add, currency); } void MyMoneyFile::modifyCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // force reload of base currency object if (currency.id() == d->m_baseCurrency.id()) d->m_baseCurrency.clearId(); d->m_storage->modifyCurrency(currency); d->addCacheNotification(currency.id()); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, currency); } void MyMoneyFile::removeCurrency(const MyMoneySecurity& currency) { d->checkTransaction(Q_FUNC_INFO); if (currency.id() == d->m_baseCurrency.id()) { throw MYMONEYEXCEPTION("Cannot delete base currency."); } // FIXME check that security is not referenced by other object // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeCurrency(currency); d->addCacheNotification(currency.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, currency); } MyMoneySecurity MyMoneyFile::currency(const QString& id) const { if (id.isEmpty()) return baseCurrency(); const MyMoneySecurity& curr = d->m_cache.security(id); if (curr.id().isEmpty()) { QString msg; msg = QString("Currency '%1' not found.").arg(id); throw MYMONEYEXCEPTION(msg); } return curr; } QMap MyMoneyFile::ancientCurrencies() const { QMap ancientCurrencies; ancientCurrencies.insert(MyMoneySecurity("ATS", i18n("Austrian Schilling"), QString::fromUtf8("ÖS")), MyMoneyPrice("ATS", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 137603), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("DEM", i18n("German Mark"), "DM"), MyMoneyPrice("ATS", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 195583), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("FRF", i18n("French Franc"), "FF"), MyMoneyPrice("FRF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 655957), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("ITL", i18n("Italian Lira"), QChar(0x20A4)), MyMoneyPrice("ITL", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100, 193627), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("ESP", i18n("Spanish Peseta"), QString()), MyMoneyPrice("ESP", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000, 166386), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("NLG", i18n("Dutch Guilder"), QString()), MyMoneyPrice("NLG", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 220371), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("BEF", i18n("Belgian Franc"), "Fr"), MyMoneyPrice("BEF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 403399), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("LUF", i18n("Luxembourg Franc"), "Fr"), MyMoneyPrice("LUF", "EUR", QDate(1998, 12, 31), MyMoneyMoney(10000, 403399), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("PTE", i18n("Portuguese Escudo"), QString()), MyMoneyPrice("PTE", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000, 200482), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("IEP", i18n("Irish Pound"), QChar(0x00A3)), MyMoneyPrice("IEP", "EUR", QDate(1998, 12, 31), MyMoneyMoney(1000000, 787564), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("FIM", i18n("Finnish Markka"), QString()), MyMoneyPrice("FIM", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100000, 594573), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("GRD", i18n("Greek Drachma"), QChar(0x20AF)), MyMoneyPrice("GRD", "EUR", QDate(1998, 12, 31), MyMoneyMoney(100, 34075), QLatin1Literal("KMyMoney"))); // http://en.wikipedia.org/wiki/Bulgarian_lev ancientCurrencies.insert(MyMoneySecurity("BGL", i18n("Bulgarian Lev"), "BGL"), MyMoneyPrice("BGL", "BGN", QDate(1999, 7, 5), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("ROL", i18n("Romanian Leu"), "ROL"), MyMoneyPrice("ROL", "RON", QDate(2005, 6, 30), MyMoneyMoney(1, 10000), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("RUR", i18n("Russian Ruble (old)"), "RUR"), MyMoneyPrice("RUR", "RUB", QDate(1998, 1, 1), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); ancientCurrencies.insert(MyMoneySecurity("SIT", i18n("Slovenian Tolar"), "SIT"), MyMoneyPrice("SIT", "EUR", QDate(2006, 12, 31), MyMoneyMoney(1, 23964), QLatin1Literal("KMyMoney"))); // Source: http://www.tf-portfoliosolutions.net/products/turkishlira.aspx ancientCurrencies.insert(MyMoneySecurity("TRL", i18n("Turkish Lira (old)"), "TL"), MyMoneyPrice("TRL", "TRY", QDate(2004, 12, 31), MyMoneyMoney(1, 1000000), QLatin1Literal("KMyMoney"))); // Source: http://www.focus.de/finanzen/news/malta-und-zypern_aid_66058.html ancientCurrencies.insert(MyMoneySecurity("MTL", i18n("Maltese Lira"), "MTL"), MyMoneyPrice("MTL", "EUR", QDate(2008, 1, 1), MyMoneyMoney(429300, 1000000), QLatin1Literal("KMyMoney"))); 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"))); // Source: http://www.focus.de/finanzen/news/waehrungszone-slowakei-ist-neuer-euro-staat_aid_359025.html ancientCurrencies.insert(MyMoneySecurity("SKK", i18n("Slovak Koruna"), "SKK"), MyMoneyPrice("SKK", "EUR", QDate(2008, 12, 31), MyMoneyMoney(1000, 30126), QLatin1Literal("KMyMoney"))); // Source: http://en.wikipedia.org/wiki/Mozambican_metical ancientCurrencies.insert(MyMoneySecurity("MZM", i18n("Mozambique Metical"), "MT"), MyMoneyPrice("MZM", "MZN", QDate(2006, 7, 1), MyMoneyMoney(1, 1000), QLatin1Literal("KMyMoney"))); // Source https://en.wikipedia.org/wiki/Azerbaijani_manat ancientCurrencies.insert(MyMoneySecurity("AZM", i18n("Azerbaijani Manat"), "m."), MyMoneyPrice("AZM", "AZN", QDate(2006, 1, 1), MyMoneyMoney(1, 5000), QLatin1Literal("KMyMoney"))); // Source: https://en.wikipedia.org/wiki/Litas ancientCurrencies.insert(MyMoneySecurity("LTL", i18n("Lithuanian Litas"), "Lt"), MyMoneyPrice("LTL", "EUR", QDate(2015, 1, 1), MyMoneyMoney(100000, 345280), QLatin1Literal("KMyMoney"))); // Source: https://en.wikipedia.org/wiki/Belarusian_ruble ancientCurrencies.insert(MyMoneySecurity("BYR", i18n("Belarusian Ruble (old)"), "BYR"), MyMoneyPrice("BYR", "BYN", QDate(2016, 7, 1), MyMoneyMoney(1, 10000), QLatin1Literal("KMyMoney"))); return ancientCurrencies; } QList MyMoneyFile::availableCurrencyList() const { QList currencyList; currencyList.append(MyMoneySecurity("AFA", i18n("Afghanistan Afghani"))); currencyList.append(MyMoneySecurity("ALL", i18n("Albanian Lek"))); currencyList.append(MyMoneySecurity("ANG", i18n("Netherland Antillian Guilder"))); currencyList.append(MyMoneySecurity("DZD", i18n("Algerian Dinar"))); currencyList.append(MyMoneySecurity("ADF", i18n("Andorran Franc"))); currencyList.append(MyMoneySecurity("ADP", i18n("Andorran Peseta"))); currencyList.append(MyMoneySecurity("AON", i18n("Angolan New Kwanza"))); currencyList.append(MyMoneySecurity("ARS", i18n("Argentine Peso"), "$")); currencyList.append(MyMoneySecurity("AWG", i18n("Aruban Florin"))); currencyList.append(MyMoneySecurity("AUD", i18n("Australian Dollar"), "$")); currencyList.append(MyMoneySecurity("AZN", i18n("Azerbaijani Manat"), "m.")); currencyList.append(MyMoneySecurity("BSD", i18n("Bahamian Dollar"), "$")); currencyList.append(MyMoneySecurity("BHD", i18n("Bahraini Dinar"), "BHD", 1000)); currencyList.append(MyMoneySecurity("BDT", i18n("Bangladeshi Taka"))); currencyList.append(MyMoneySecurity("BBD", i18n("Barbados Dollar"), "$")); currencyList.append(MyMoneySecurity("BTC", i18n("Bitcoin"), "BTC")); currencyList.append(MyMoneySecurity("BYN", i18n("Belarusian Ruble"), "Br")); currencyList.append(MyMoneySecurity("BZD", i18n("Belize Dollar"), "$")); currencyList.append(MyMoneySecurity("BMD", i18n("Bermudian Dollar"), "$")); currencyList.append(MyMoneySecurity("BTN", i18n("Bhutan Ngultrum"))); currencyList.append(MyMoneySecurity("BOB", i18n("Bolivian Boliviano"))); currencyList.append(MyMoneySecurity("BAM", i18n("Bosnian Convertible Mark"))); currencyList.append(MyMoneySecurity("BWP", i18n("Botswana Pula"))); currencyList.append(MyMoneySecurity("BRL", i18n("Brazilian Real"), "R$")); currencyList.append(MyMoneySecurity("GBP", i18n("British Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("BND", i18n("Brunei Dollar"), "$")); currencyList.append(MyMoneySecurity("BGN", i18n("Bulgarian Lev (new)"))); currencyList.append(MyMoneySecurity("BIF", i18n("Burundi Franc"))); currencyList.append(MyMoneySecurity("XAF", i18n("CFA Franc BEAC"))); currencyList.append(MyMoneySecurity("XOF", i18n("CFA Franc BCEAO"))); currencyList.append(MyMoneySecurity("XPF", i18n("CFP Franc Pacifique"), "F", 1, 100)); currencyList.append(MyMoneySecurity("KHR", i18n("Cambodia Riel"))); currencyList.append(MyMoneySecurity("CAD", i18n("Canadian Dollar"), "$")); currencyList.append(MyMoneySecurity("CVE", i18n("Cape Verde Escudo"))); currencyList.append(MyMoneySecurity("KYD", i18n("Cayman Islands Dollar"), "$")); currencyList.append(MyMoneySecurity("CLP", i18n("Chilean Peso"))); currencyList.append(MyMoneySecurity("CNY", i18n("Chinese Yuan Renminbi"))); currencyList.append(MyMoneySecurity("COP", i18n("Colombian Peso"))); currencyList.append(MyMoneySecurity("KMF", i18n("Comoros Franc"))); currencyList.append(MyMoneySecurity("CRC", i18n("Costa Rican Colon"), QChar(0x20A1))); currencyList.append(MyMoneySecurity("HRK", i18n("Croatian Kuna"))); currencyList.append(MyMoneySecurity("CUP", i18n("Cuban Peso"))); currencyList.append(MyMoneySecurity("CZK", i18n("Czech Koruna"))); currencyList.append(MyMoneySecurity("DKK", i18n("Danish Krone"), "kr")); currencyList.append(MyMoneySecurity("DJF", i18n("Djibouti Franc"))); currencyList.append(MyMoneySecurity("DOP", i18n("Dominican Peso"))); currencyList.append(MyMoneySecurity("XCD", i18n("East Caribbean Dollar"), "$")); currencyList.append(MyMoneySecurity("EGP", i18n("Egyptian Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("SVC", i18n("El Salvador Colon"))); currencyList.append(MyMoneySecurity("ERN", i18n("Eritrean Nakfa"))); currencyList.append(MyMoneySecurity("EEK", i18n("Estonian Kroon"))); currencyList.append(MyMoneySecurity("ETB", i18n("Ethiopian Birr"))); currencyList.append(MyMoneySecurity("EUR", i18n("Euro"), QChar(0x20ac))); currencyList.append(MyMoneySecurity("FKP", i18n("Falkland Islands Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("FJD", i18n("Fiji Dollar"), "$")); currencyList.append(MyMoneySecurity("GMD", i18n("Gambian Dalasi"))); currencyList.append(MyMoneySecurity("GEL", i18n("Georgian Lari"))); currencyList.append(MyMoneySecurity("GHC", i18n("Ghanaian Cedi"))); currencyList.append(MyMoneySecurity("GIP", i18n("Gibraltar Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("GTQ", i18n("Guatemalan Quetzal"))); currencyList.append(MyMoneySecurity("GWP", i18n("Guinea-Bissau Peso"))); currencyList.append(MyMoneySecurity("GYD", i18n("Guyanan Dollar"), "$")); currencyList.append(MyMoneySecurity("HTG", i18n("Haitian Gourde"))); currencyList.append(MyMoneySecurity("HNL", i18n("Honduran Lempira"))); currencyList.append(MyMoneySecurity("HKD", i18n("Hong Kong Dollar"), "$")); currencyList.append(MyMoneySecurity("HUF", i18n("Hungarian Forint"), "HUF", 1, 100)); currencyList.append(MyMoneySecurity("ISK", i18n("Iceland Krona"))); currencyList.append(MyMoneySecurity("INR", i18n("Indian Rupee"), QChar(0x20A8))); currencyList.append(MyMoneySecurity("IDR", i18n("Indonesian Rupiah"), "IDR", 1)); currencyList.append(MyMoneySecurity("IRR", i18n("Iranian Rial"), "IRR", 1)); currencyList.append(MyMoneySecurity("IQD", i18n("Iraqi Dinar"), "IQD", 1000)); currencyList.append(MyMoneySecurity("ILS", i18n("Israeli New Shekel"), QChar(0x20AA))); currencyList.append(MyMoneySecurity("JMD", i18n("Jamaican Dollar"), "$")); currencyList.append(MyMoneySecurity("JPY", i18n("Japanese Yen"), QChar(0x00A5), 1)); currencyList.append(MyMoneySecurity("JOD", i18n("Jordanian Dinar"), "JOD", 1000)); currencyList.append(MyMoneySecurity("KZT", i18n("Kazakhstan Tenge"))); currencyList.append(MyMoneySecurity("KES", i18n("Kenyan Shilling"))); currencyList.append(MyMoneySecurity("KWD", i18n("Kuwaiti Dinar"), "KWD", 1000)); currencyList.append(MyMoneySecurity("KGS", i18n("Kyrgyzstan Som"))); currencyList.append(MyMoneySecurity("LAK", i18n("Laos Kip"), QChar(0x20AD))); currencyList.append(MyMoneySecurity("LVL", i18n("Latvian Lats"))); currencyList.append(MyMoneySecurity("LBP", i18n("Lebanese Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("LSL", i18n("Lesotho Loti"))); currencyList.append(MyMoneySecurity("LRD", i18n("Liberian Dollar"), "$")); currencyList.append(MyMoneySecurity("LYD", i18n("Libyan Dinar"), "LYD", 1000)); currencyList.append(MyMoneySecurity("MOP", i18n("Macau Pataca"))); currencyList.append(MyMoneySecurity("MKD", i18n("Macedonian Denar"))); currencyList.append(MyMoneySecurity("MGF", i18n("Malagasy Franc"), "MGF", 500)); currencyList.append(MyMoneySecurity("MWK", i18n("Malawi Kwacha"))); currencyList.append(MyMoneySecurity("MYR", i18n("Malaysian Ringgit"))); currencyList.append(MyMoneySecurity("MVR", i18n("Maldive Rufiyaa"))); currencyList.append(MyMoneySecurity("MLF", i18n("Mali Republic Franc"))); currencyList.append(MyMoneySecurity("MRO", i18n("Mauritanian Ouguiya"), "MRO", 5)); currencyList.append(MyMoneySecurity("MUR", i18n("Mauritius Rupee"))); currencyList.append(MyMoneySecurity("MXN", i18n("Mexican Peso"), "$")); currencyList.append(MyMoneySecurity("MDL", i18n("Moldavian Leu"))); currencyList.append(MyMoneySecurity("MNT", i18n("Mongolian Tugrik"), QChar(0x20AE))); currencyList.append(MyMoneySecurity("MAD", i18n("Moroccan Dirham"))); currencyList.append(MyMoneySecurity("MZN", i18n("Mozambique Metical"), "MT")); currencyList.append(MyMoneySecurity("MMK", i18n("Myanmar Kyat"))); currencyList.append(MyMoneySecurity("NAD", i18n("Namibian Dollar"), "$")); currencyList.append(MyMoneySecurity("NPR", i18n("Nepalese Rupee"))); currencyList.append(MyMoneySecurity("NZD", i18n("New Zealand Dollar"), "$")); currencyList.append(MyMoneySecurity("NIC", i18n("Nicaraguan Cordoba Oro"))); currencyList.append(MyMoneySecurity("NGN", i18n("Nigerian Naira"), QChar(0x20A6))); currencyList.append(MyMoneySecurity("KPW", i18n("North Korean Won"), QChar(0x20A9))); currencyList.append(MyMoneySecurity("NOK", i18n("Norwegian Kroner"), "kr")); currencyList.append(MyMoneySecurity("OMR", i18n("Omani Rial"), "OMR", 1000)); currencyList.append(MyMoneySecurity("PKR", i18n("Pakistan Rupee"))); currencyList.append(MyMoneySecurity("PAB", i18n("Panamanian Balboa"))); currencyList.append(MyMoneySecurity("PGK", i18n("Papua New Guinea Kina"))); currencyList.append(MyMoneySecurity("PYG", i18n("Paraguay Guarani"))); currencyList.append(MyMoneySecurity("PEN", i18n("Peruvian Nuevo Sol"))); currencyList.append(MyMoneySecurity("PHP", i18n("Philippine Peso"), QChar(0x20B1))); currencyList.append(MyMoneySecurity("PLN", i18n("Polish Zloty"))); currencyList.append(MyMoneySecurity("QAR", i18n("Qatari Rial"))); currencyList.append(MyMoneySecurity("RON", i18n("Romanian Leu (new)"))); currencyList.append(MyMoneySecurity("RUB", i18n("Russian Ruble"))); currencyList.append(MyMoneySecurity("RWF", i18n("Rwanda Franc"))); currencyList.append(MyMoneySecurity("WST", i18n("Samoan Tala"))); currencyList.append(MyMoneySecurity("STD", i18n("Sao Tome and Principe Dobra"))); currencyList.append(MyMoneySecurity("SAR", i18n("Saudi Riyal"))); currencyList.append(MyMoneySecurity("RSD", i18n("Serbian Dinar"))); currencyList.append(MyMoneySecurity("SCR", i18n("Seychelles Rupee"))); currencyList.append(MyMoneySecurity("SLL", i18n("Sierra Leone Leone"))); currencyList.append(MyMoneySecurity("SGD", i18n("Singapore Dollar"), "$")); currencyList.append(MyMoneySecurity("SBD", i18n("Solomon Islands Dollar"), "$")); currencyList.append(MyMoneySecurity("SOS", i18n("Somali Shilling"))); currencyList.append(MyMoneySecurity("ZAR", i18n("South African Rand"))); currencyList.append(MyMoneySecurity("KRW", i18n("South Korean Won"), QChar(0x20A9))); currencyList.append(MyMoneySecurity("LKR", i18n("Sri Lanka Rupee"))); currencyList.append(MyMoneySecurity("SHP", i18n("St. Helena Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("SDD", i18n("Sudanese Dinar"))); currencyList.append(MyMoneySecurity("SRG", i18n("Suriname Guilder"))); currencyList.append(MyMoneySecurity("SZL", i18n("Swaziland Lilangeni"))); currencyList.append(MyMoneySecurity("SEK", i18n("Swedish Krona"))); currencyList.append(MyMoneySecurity("CHF", i18n("Swiss Franc"), "SFr")); currencyList.append(MyMoneySecurity("SYP", i18n("Syrian Pound"), QChar(0x00A3))); currencyList.append(MyMoneySecurity("TWD", i18n("Taiwan Dollar"), "$")); currencyList.append(MyMoneySecurity("TJS", i18n("Tajikistan Somoni"))); currencyList.append(MyMoneySecurity("TZS", i18n("Tanzanian Shilling"))); currencyList.append(MyMoneySecurity("THB", i18n("Thai Baht"), QChar(0x0E3F))); currencyList.append(MyMoneySecurity("TOP", i18n("Tongan Pa'anga"))); currencyList.append(MyMoneySecurity("TTD", i18n("Trinidad and Tobago Dollar"), "$")); currencyList.append(MyMoneySecurity("TND", i18n("Tunisian Dinar"), "TND", 1000)); currencyList.append(MyMoneySecurity("TRY", i18n("Turkish Lira"), QChar(0x20BA))); currencyList.append(MyMoneySecurity("TMM", i18n("Turkmenistan Manat"))); currencyList.append(MyMoneySecurity("USD", i18n("US Dollar"), "$")); currencyList.append(MyMoneySecurity("UGX", i18n("Uganda Shilling"))); currencyList.append(MyMoneySecurity("UAH", i18n("Ukraine Hryvnia"))); currencyList.append(MyMoneySecurity("CLF", i18n("Unidad de Fometo"))); currencyList.append(MyMoneySecurity("AED", i18n("United Arab Emirates Dirham"))); currencyList.append(MyMoneySecurity("UYU", i18n("Uruguayan Peso"))); currencyList.append(MyMoneySecurity("UZS", i18n("Uzbekistani Sum"))); currencyList.append(MyMoneySecurity("VUV", i18n("Vanuatu Vatu"))); currencyList.append(MyMoneySecurity("VEB", i18n("Venezuelan Bolivar"))); currencyList.append(MyMoneySecurity("VND", i18n("Vietnamese Dong"), QChar(0x20AB))); currencyList.append(MyMoneySecurity("ZMK", i18n("Zambian Kwacha"))); currencyList.append(MyMoneySecurity("ZWD", i18n("Zimbabwe Dollar"), "$")); currencyList.append(ancientCurrencies().keys()); // sort the currencies ... qSort(currencyList.begin(), currencyList.end(), [] (const MyMoneySecurity& c1, const MyMoneySecurity& c2) { return c1.name().compare(c2.name()) < 0; }); // ... and add a few precious metals at the ned currencyList.append(MyMoneySecurity("XAU", i18n("Gold"), "XAU", 1000000)); currencyList.append(MyMoneySecurity("XPD", i18n("Palladium"), "XPD", 1000000)); currencyList.append(MyMoneySecurity("XPT", i18n("Platinum"), "XPT", 1000000)); currencyList.append(MyMoneySecurity("XAG", i18n("Silver"), "XAG", 1000000)); return currencyList; } QList MyMoneyFile::currencyList() const { d->checkStorage(); return d->m_storage->currencyList(); } QString MyMoneyFile::foreignCurrency(const QString& first, const QString& second) const { if (baseCurrency().id() == second) return first; return second; } MyMoneySecurity MyMoneyFile::baseCurrency() const { if (d->m_baseCurrency.id().isEmpty()) { QString id = QString(value("kmm-baseCurrency")); if (!id.isEmpty()) d->m_baseCurrency = currency(id); } return d->m_baseCurrency; } void MyMoneyFile::setBaseCurrency(const MyMoneySecurity& curr) { // make sure the currency exists MyMoneySecurity c = currency(curr.id()); // clear all changed objects from cache MyMoneyNotifier notifier(d); if (c.id() != d->m_baseCurrency.id()) { setValue("kmm-baseCurrency", curr.id()); // force reload of base currency cache d->m_baseCurrency = MyMoneySecurity(); } } void MyMoneyFile::addPrice(const MyMoneyPrice& price) { if (price.rate(QString()).isZero()) return; d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // store the account's which are affected by this price regarding their value d->priceChanged(*this, price); d->m_storage->addPrice(price); } void MyMoneyFile::removePrice(const MyMoneyPrice& price) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); // store the account's which are affected by this price regarding their value d->priceChanged(*this, price); d->m_storage->removePrice(price); } MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate) const { d->checkStorage(); QString to(toId); if (to.isEmpty()) to = value("kmm-baseCurrency"); // if some id is missing, we can return an empty price object if (fromId.isEmpty() || to.isEmpty()) return MyMoneyPrice(); // we don't search our tables if someone asks stupid stuff if (fromId == toId) { return MyMoneyPrice(fromId, toId, date, MyMoneyMoney::ONE, "KMyMoney"); } // if not asking for exact date, try to find the exact date match first, // either the requested price or its reciprocal value. If unsuccessful, it will move // on and look for prices of previous dates MyMoneyPrice rc = d->m_storage->price(fromId, to, date, true); if (!rc.isValid()) { // not found, search 'to-from' rate and use reciprocal value rc = d->m_storage->price(to, fromId, date, true); // not found, search previous dates, if exact date is not needed if (!exactDate && !rc.isValid()) { // search 'from-to' and 'to-from', select the most recent one MyMoneyPrice fromPrice = d->m_storage->price(fromId, to, date, exactDate); MyMoneyPrice toPrice = d->m_storage->price(to, fromId, date, exactDate); // check first whether both prices are valid if (fromPrice.isValid() && toPrice.isValid()) { if (fromPrice.date() >= toPrice.date()) { // if 'from-to' is newer or the same date, prefer that one rc = fromPrice; } else { // otherwise, use the reciprocal price rc = toPrice; } } else if (fromPrice.isValid()) { // check if any of the prices is valid, return that one rc = fromPrice; } else if (toPrice.isValid()) { rc = toPrice; } } } return rc; } MyMoneyPrice MyMoneyFile::price(const QString& fromId, const QString& toId) const { return price(fromId, toId, QDate::currentDate(), false); } MyMoneyPrice MyMoneyFile::price(const QString& fromId) const { return price(fromId, QString(), QDate::currentDate(), false); } MyMoneyPriceList MyMoneyFile::priceList() const { d->checkStorage(); return d->m_storage->priceList(); } bool MyMoneyFile::hasAccount(const QString& id, const QString& name) const { auto acc = d->m_cache.account(id); auto rc = false; foreach (const auto sAccount, acc.accountList()) { auto a = d->m_cache.account(sAccount); if (a.name() == name) rc = true; } return rc; } QList MyMoneyFile::reportList() const { d->checkStorage(); return d->m_storage->reportList(); } void MyMoneyFile::addReport(MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addReport(report); } void MyMoneyFile::modifyReport(const MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifyReport(report); d->addCacheNotification(report.id()); } unsigned MyMoneyFile::countReports() const { d->checkStorage(); return d->m_storage->countReports(); } MyMoneyReport MyMoneyFile::report(const QString& id) const { d->checkStorage(); return d->m_storage->report(id); } void MyMoneyFile::removeReport(const MyMoneyReport& report) { d->checkTransaction(Q_FUNC_INFO); MyMoneyNotifier notifier(d); d->m_storage->removeReport(report); d->addCacheNotification(report.id(), false); } QList MyMoneyFile::budgetList() const { d->checkStorage(); return d->m_storage->budgetList(); } void MyMoneyFile::addBudget(MyMoneyBudget &budget) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addBudget(budget); } MyMoneyBudget MyMoneyFile::budgetByName(const QString& name) const { d->checkStorage(); return d->m_storage->budgetByName(name); } void MyMoneyFile::modifyBudget(const MyMoneyBudget& budget) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->modifyBudget(budget); d->addCacheNotification(budget.id()); } unsigned MyMoneyFile::countBudgets() const { d->checkStorage(); return d->m_storage->countBudgets(); } MyMoneyBudget MyMoneyFile::budget(const QString& id) const { d->checkStorage(); return d->m_storage->budget(id); } void MyMoneyFile::removeBudget(const MyMoneyBudget& budget) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->removeBudget(budget); d->addCacheNotification(budget.id(), false); } void MyMoneyFile::addOnlineJob(onlineJob& job) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); d->m_storage->addOnlineJob(job); d->m_cache.preloadOnlineJob(job); d->m_changeSet += MyMoneyNotification(File::Mode::Add, job); } void MyMoneyFile::modifyOnlineJob(const onlineJob job) { d->checkTransaction(Q_FUNC_INFO); d->m_storage->modifyOnlineJob(job); d->m_changeSet += MyMoneyNotification(File::Mode::Modify, job); d->addCacheNotification(job.id()); } onlineJob MyMoneyFile::getOnlineJob(const QString &jobId) const { d->checkStorage(); return d->m_storage->getOnlineJob(jobId); } QList MyMoneyFile::onlineJobList() const { d->checkStorage(); return d->m_storage->onlineJobList(); } /** @todo improve speed by passing count job to m_storage */ int MyMoneyFile::countOnlineJobs() const { return onlineJobList().count(); } /** * @brief Remove onlineJob * @param job onlineJob to remove */ void MyMoneyFile::removeOnlineJob(const onlineJob& job) { d->checkTransaction(Q_FUNC_INFO); // clear all changed objects from cache MyMoneyNotifier notifier(d); if (job.isLocked()) { return; } d->addCacheNotification(job.id(), false); d->m_changeSet += MyMoneyNotification(File::Mode::Remove, job); d->m_storage->removeOnlineJob(job); } void MyMoneyFile::removeOnlineJob(const QStringList onlineJobIds) { foreach (QString jobId, onlineJobIds) { removeOnlineJob(getOnlineJob(jobId)); } } void MyMoneyFile::costCenterList(QList< MyMoneyCostCenter >& list) const { d->checkStorage(); list = d->m_storage->costCenterList(); } bool MyMoneyFile::addVATSplit(MyMoneyTransaction& transaction, const MyMoneyAccount& account, const MyMoneyAccount& category, const MyMoneyMoney& amount) { bool rc = false; try { MyMoneySplit cat; // category MyMoneySplit tax; // tax if (category.value("VatAccount").isEmpty()) return false; MyMoneyAccount vatAcc = this->account(category.value("VatAccount").toLatin1()); const MyMoneySecurity& asec = security(account.currencyId()); const MyMoneySecurity& csec = security(category.currencyId()); const MyMoneySecurity& vsec = security(vatAcc.currencyId()); if (asec.id() != csec.id() || asec.id() != vsec.id()) { qDebug("Auto VAT assignment only works if all three accounts use the same currency."); return false; } MyMoneyMoney vatRate(vatAcc.value("VatRate")); MyMoneyMoney gv, nv; // gross value, net value int fract = account.fraction(); if (!vatRate.isZero()) { tax.setAccountId(vatAcc.id()); // qDebug("vat amount is '%s'", category.value("VatAmount").toLatin1()); if (category.value("VatAmount").toLower() != QString("net")) { // split value is the gross value gv = amount; nv = (gv / (MyMoneyMoney::ONE + vatRate)).convert(fract); MyMoneySplit catSplit = transaction.splitByAccount(account.id(), false); catSplit.setShares(-nv); catSplit.setValue(catSplit.shares()); transaction.modifySplit(catSplit); } else { // split value is the net value nv = amount; gv = (nv * (MyMoneyMoney::ONE + vatRate)).convert(fract); MyMoneySplit accSplit = transaction.splitByAccount(account.id()); accSplit.setValue(gv.convert(fract)); accSplit.setShares(accSplit.value()); transaction.modifySplit(accSplit); } tax.setValue(-(gv - nv).convert(fract)); tax.setShares(tax.value()); transaction.addSplit(tax); rc = true; } } catch (const MyMoneyException &) { } return rc; } bool MyMoneyFile::isReferenced(const MyMoneyObject& obj, const QBitArray& skipChecks) const { d->checkStorage(); return d->m_storage->isReferenced(obj, skipChecks); } bool MyMoneyFile::isReferenced(const MyMoneyObject& obj) const { return isReferenced(obj, QBitArray((int)eStorage::Reference::Count)); } bool MyMoneyFile::checkNoUsed(const QString& accId, const QString& no) const { // by definition, an empty string or a non-numeric string is not used QRegExp exp(QString("(.*\\D)?(\\d+)(\\D.*)?")); if (no.isEmpty() || exp.indexIn(no) == -1) return false; MyMoneyTransactionFilter filter; filter.addAccount(accId); QList transactions = transactionList(filter); QList::ConstIterator it_t = transactions.constBegin(); while (it_t != transactions.constEnd()) { try { MyMoneySplit split; // Test whether the transaction also includes a split into // this account split = (*it_t).splitByAccount(accId, true /*match*/); if (!split.number().isEmpty() && split.number() == no) return true; } catch (const MyMoneyException &) { } ++it_t; } return false; } QString MyMoneyFile::highestCheckNo(const QString& accId) const { unsigned64 lno = 0; unsigned64 cno; QString no; MyMoneyTransactionFilter filter; filter.addAccount(accId); QList transactions = transactionList(filter); QList::ConstIterator it_t = transactions.constBegin(); while (it_t != transactions.constEnd()) { try { // Test whether the transaction also includes a split into // this account MyMoneySplit split = (*it_t).splitByAccount(accId, true /*match*/); if (!split.number().isEmpty()) { // non-numerical values stored in number will return 0 in the next line cno = split.number().toULongLong(); if (cno > lno) { lno = cno; no = split.number(); } } } catch (const MyMoneyException &) { } ++it_t; } return no; } bool MyMoneyFile::hasNewerTransaction(const QString& accId, const QDate& date) const { MyMoneyTransactionFilter filter; filter.addAccount(accId); filter.setDateFilter(date.addDays(+1), QDate()); return !transactionList(filter).isEmpty(); } void MyMoneyFile::clearCache() { d->checkStorage(); d->m_cache.clear(); d->m_balanceCache.clear(); } void MyMoneyFile::forceDataChanged() { emit dataChanged(); } void MyMoneyFile::preloadCache() { d->checkStorage(); d->m_cache.clear(); QList a_list; d->m_storage->accountList(a_list); d->m_cache.preloadAccount(a_list); d->m_cache.preloadPayee(d->m_storage->payeeList()); d->m_cache.preloadTag(d->m_storage->tagList()); d->m_cache.preloadInstitution(d->m_storage->institutionList()); d->m_cache.preloadSecurity(d->m_storage->securityList() + d->m_storage->currencyList()); d->m_cache.preloadSchedule(d->m_storage->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false)); d->m_cache.preloadOnlineJob(d->m_storage->onlineJobList()); } bool MyMoneyFile::isTransfer(const MyMoneyTransaction& t) const { auto rc = false; if (t.splitCount() == 2) { foreach (const auto split, t.splits()) { auto acc = account(split.accountId()); if (acc.isIncomeExpense()) { rc = true; break; } } } return rc; } bool MyMoneyFile::referencesClosedAccount(const MyMoneyTransaction& t) const { auto ret = false; foreach (const auto split, t.splits()) { if (referencesClosedAccount(split)) { ret = true; break; } } return ret; } bool MyMoneyFile::referencesClosedAccount(const MyMoneySplit& s) const { if (s.accountId().isEmpty()) return false; try { return account(s.accountId()).isClosed(); } catch (const MyMoneyException &) { } return false; } QString MyMoneyFile::storageId() { QString id = value("kmm-id"); if (id.isEmpty()) { MyMoneyFileTransaction ft; try { QUuid uid = QUuid::createUuid(); setValue("kmm-id", uid.toString()); ft.commit(); id = uid.toString(); } catch (const MyMoneyException &) { qDebug("Unable to setup UID for new storage object"); } } return id; } QString MyMoneyFile::openingBalancesPrefix() { return i18n("Opening Balances"); } bool MyMoneyFile::hasMatchingOnlineBalance(const MyMoneyAccount& _acc) const { // get current values auto acc = account(_acc.id()); // if there's no last transaction import data we are done if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty()) return false; // otherwise, we compare the balances MyMoneyMoney balance(acc.value("lastStatementBalance")); MyMoneyMoney accBalance = this->balance(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate)); return balance == accBalance; } int MyMoneyFile::countTransactionsWithSpecificReconciliationState(const QString& accId, TransactionFilter::State state) const { MyMoneyTransactionFilter filter; filter.addAccount(accId); filter.addState((int)state); return transactionList(filter).count(); } /** * Make sure that the splits value has the precision of the corresponding account */ void MyMoneyFile::fixSplitPrecision(MyMoneyTransaction& t) const { auto transactionSecurity = security(t.commodity()); auto transactionFraction = transactionSecurity.smallestAccountFraction(); for (auto& split : t.splits()) { auto acc = account(split.accountId()); auto fraction = acc.fraction(); if(fraction == -1) { auto sec = security(acc.currencyId()); fraction = acc.fraction(sec); } split.setShares(static_cast(split.shares().convertDenominator(fraction).canonicalize())); split.setValue(static_cast(split.value().convertDenominator(transactionFraction).canonicalize())); } } class MyMoneyFileTransactionPrivate { Q_DISABLE_COPY(MyMoneyFileTransactionPrivate) public: MyMoneyFileTransactionPrivate() : m_isNested(MyMoneyFile::instance()->hasTransaction()), m_needRollback(!m_isNested) { } public: bool m_isNested; bool m_needRollback; }; MyMoneyFileTransaction::MyMoneyFileTransaction() : d_ptr(new MyMoneyFileTransactionPrivate) { Q_D(MyMoneyFileTransaction); if (!d->m_isNested) MyMoneyFile::instance()->startTransaction(); } MyMoneyFileTransaction::~MyMoneyFileTransaction() { rollback(); Q_D(MyMoneyFileTransaction); delete d; } void MyMoneyFileTransaction::restart() { rollback(); Q_D(MyMoneyFileTransaction); d->m_needRollback = !d->m_isNested; if (!d->m_isNested) MyMoneyFile::instance()->startTransaction(); } void MyMoneyFileTransaction::commit() { Q_D(MyMoneyFileTransaction); if (!d->m_isNested) MyMoneyFile::instance()->commitTransaction(); d->m_needRollback = false; } void MyMoneyFileTransaction::rollback() { Q_D(MyMoneyFileTransaction); if (d->m_needRollback) MyMoneyFile::instance()->rollbackTransaction(); d->m_needRollback = false; } diff --git a/kmymoney/mymoney/mymoneyfile.h b/kmymoney/mymoney/mymoneyfile.h index b707ccdbc..4e519b226 100644 --- a/kmymoney/mymoney/mymoneyfile.h +++ b/kmymoney/mymoney/mymoneyfile.h @@ -1,1711 +1,1711 @@ /*************************************************************************** mymoneyfile.h ------------------- copyright : (C) 2002, 2007 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYFILE_H #define MYMONEYFILE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "kmm_mymoney_export.h" #include "mymoneyunittestable.h" /** * @author Thomas Baumgart, Michael Edwardes, Kevin Tambascio, Christian Dávid */ /** * This class represents the interface to the MyMoney engine. * For historical reasons it is still called MyMoneyFile. * It is implemented using the singleton pattern and thus only * exists once for each running instance of an application. * * The instance of the MyMoneyFile object is accessed as follows: * * @code * MyMoneyFile *file = MyMoneyFile::instance(); * file->anyMemberFunction(); * @endcode * * The first line of the above code creates a unique MyMoneyFile * object if it is called for the first time ever. All subsequent * calls to this functions return a pointer to the object created * during the first call. * * As the MyMoneyFile object represents the business logic, a storage * manager must be attached to it. This mechanism allows to use different * access methods to store the objects. The interface to access such an - * storage manager is defined in the class IMyMoneyStorage. The methods + * storage manager is defined in the class MyMoneyStorageMgr. The methods * attachStorage() and detachStorage() are used to attach/detach a * storage manager object. The following code can be used to create a * functional MyMoneyFile instance: * * @code - * IMyMoneyStorage *storage = .... + * MyMoneyStorageMgr *storage = .... * MyMoneyFile *file = MyMoneyFile::instance(); * file->attachStorage(storage); * @endcode * * The methods addAccount(), modifyAccount() and removeAccount() implement the * general account maintenance functions. The method reparentAccount() is * available to move an account from one superordinate account to another. * account() and accountList() are used to retrieve a single instance or a * QList of MyMoneyAccount objects. * * The methods addInstitution(), modifyInstitution() and removeInstitution() * implement the general institution maintenance functions. institution() and * institutionList() are used to retrieve a single instance or a * QList of MyMoneyInstitution objects. * * The methods addPayee(), modifyPayee() and removePayee() * implement the general payee maintenance functions. * payee() and payeeList() are used to retrieve a single instance or a * QList of MyMoneyPayee objects. * * The methods addTag(), modifyTag() and removeTag() * implement the general tag maintenance functions. * tag() and tagList() are used to retrieve a single instance or a * QList of MyMoneyTag objects. * * The methods addTransaction(), modifyTransaction() and removeTransaction() * implement the general transaction maintenance functions. * transaction() and transactionList() are used to retrieve * a single instance or a QList of MyMoneyTransaction objects. * * The methods addSecurity(), modifySecurity() and removeSecurity() * implement the general access to equities held in the engine. * * The methods addCurrency(), modifyCurrency() and removeCurrency() * implement the general access to multiple currencies held in the engine. * The methods baseCurrency() and setBaseCurrency() allow to retrieve/set * the currency selected by the user as base currency. If a currency * reference is emtpy, it will usually be interpreted as baseCurrency(). * * The methods liability(), asset(), expense(), income() and equity() are * used to retrieve the five standard accounts. isStandardAccount() * checks if a given accountId references one of the or not. * setAccountName() is used to specify a name for the standard accounts * from the GUI. * * The MyMoneyFile object emits the dataChanged() signal when data * has been changed. * * For abritrary values that have to be stored with the storage object * but are of importance to the application only, the object is derived * for MyMoneyKeyValueContainer which provides a container to store * these values indexed by an alphanumeric key. * * @exception MyMoneyException is thrown whenever an error occurs * while the engine code is running. The MyMoneyException:: object * describes the problem. */ template class QMap; class QString; class QStringList; class QBitArray; -class IMyMoneyStorage; +class MyMoneyStorageMgr; class MyMoneyCostCenter; class MyMoneyAccount; class MyMoneyInstitution; class MyMoneySecurity; class MyMoneyPrice; typedef QPair MyMoneySecurityPair; typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; class MyMoneySchedule; class MyMoneyTag; class MyMoneyPayee; class MyMoneyBudget; class MyMoneyReport; class MyMoneyMoney; class MyMoneySplit; class MyMoneyObject; class MyMoneyTransaction; class MyMoneyTransactionFilter; class onlineJob; namespace eMyMoney { namespace Account { enum class Type; } namespace File { enum class Object; } namespace Schedule { enum class Type; enum class Occurrence; enum class PaymentType; } namespace TransactionFilter { enum class State; } } class KMM_MYMONEY_EXPORT MyMoneyFile : public QObject { Q_OBJECT KMM_MYMONEY_UNIT_TESTABLE public: friend class MyMoneyNotifier; /** * This is the function to access the MyMoneyFile object. * It returns a pointer to the single instance of the object. */ static MyMoneyFile* instance(); /** * This is the destructor for any MyMoneyFile object */ ~MyMoneyFile(); /** * @deprecated This is a convenience constructor. Do not use it anymore. * It will be deprecated in a future version of the engine. * - * @param storage pointer to object that implements the IMyMoneyStorage + * @param storage pointer to object that implements the MyMoneyStorageMgr * interface. */ - explicit MyMoneyFile(IMyMoneyStorage *storage); + explicit MyMoneyFile(MyMoneyStorageMgr *storage); // general get functions MyMoneyPayee user() const; // general set functions void setUser(const MyMoneyPayee& user); /** * This method is used to attach a storage object to the MyMoneyFile object * Without an attached storage object, the MyMoneyFile object is * of no use. * * After successful completion, the dataChanged() signal is emitted. * * In case of an error condition, an exception is thrown. * The following error conditions are checked: * * - @a storage is not equal to 0 * - there is no other @a storage object attached (use detachStorage() * to revert the attachStorage() operation. * - * @param storage pointer to object that implements the IMyMoneyStorage + * @param storage pointer to object that implements the MyMoneyStorageMgr * interface. * * @sa detachStorage() */ - void attachStorage(IMyMoneyStorage* const storage); + void attachStorage(MyMoneyStorageMgr* const storage); /** * This method is used to detach a previously attached storage * object from the MyMoneyFile object. If no storage object * is attached to the engine, this is a NOP. * - * @param storage pointer to object that implements the IMyMoneyStorage + * @param storage pointer to object that implements the MyMoneyStorageMgr * interface. * * @sa attachStorage() */ - void detachStorage(IMyMoneyStorage* const storage = 0); + void detachStorage(MyMoneyStorageMgr* const storage = 0); /** * This method returns whether a storage is currently attached to * the engine or not. * * @return true if storage object is attached, false otherwise */ bool storageAttached() const; /** * This method returns a pointer to the storage object * * @return const pointer to the current attached storage object. * If no object is attached, returns 0. */ - IMyMoneyStorage* storage() const; + MyMoneyStorageMgr* storage() const; /** * This method must be called before any single change or a series of changes * in the underlying storage area is performed. * Once all changes are complete (i.e. the transaction is completed), * commitTransaction() must be called to finalize all changes. If an error occurs * during the processing of the changes call rollbackTransaction() to undo the * changes done so far. */ void startTransaction(); /** * This method returns whether a transaction has been started (@a true) * or not (@a false). */ bool hasTransaction() const; /** * @sa startTransaction() */ void commitTransaction(); /** * @sa startTransaction() */ void rollbackTransaction(); /** * This method is used to return the standard liability account * @return MyMoneyAccount liability account(group) */ MyMoneyAccount liability() const; /** * This method is used to return the standard asset account * @return MyMoneyAccount asset account(group) */ MyMoneyAccount asset() const; /** * This method is used to return the standard expense account * @return MyMoneyAccount expense account(group) */ MyMoneyAccount expense() const; /** * This method is used to return the standard income account * @return MyMoneyAccount income account(group) */ MyMoneyAccount income() const; /** * This method is used to return the standard equity account * @return MyMoneyAccount equity account(group) */ MyMoneyAccount equity() const; /** * This method returns the account information for the opening * balances account for the given @p security. If the respective * account does not exist, it will be created. The name is constructed * using MyMoneyFile::openingBalancesPrefix() and appending " (xxx)" in * case the @p security is not the baseCurrency(). The account created * will be a sub-account of the standard equity account provided by equity(). * * @param security Security for which the account is searched * * @return The opening balance account * * @note No notifications will be sent! */ MyMoneyAccount openingBalanceAccount(const MyMoneySecurity& security); /** * This method is essentially the same as the above, except it works on * const objects. If there is no opening balance account, this method * WILL NOT create one. Instead it will thrown an exception. * * @param security Security for which the account is searched * * @return The opening balance account * * @note No notifications will be sent! */ MyMoneyAccount openingBalanceAccount(const MyMoneySecurity& security) const; /** * Create an opening balance transaction for the account @p acc * with a value of @p balance. If the corresponding opening balance account * for the account's currency does not exist it will be created. If it exists * and it's opening date is later than the opening date of @p acc, * the opening date of the opening balances account will be adjusted to the * one of @p acc. * * @param acc reference to account for which the opening balance transaction * should be created * @param balance reference to the value of the opening balance transaction * * @returns The created MyMoneyTransaction object. In case no transaction has been * created, the id of the object is empty. */ MyMoneyTransaction createOpeningBalanceTransaction(const MyMoneyAccount& acc, const MyMoneyMoney& balance); /** * Retrieve the opening balance transaction for the account @p acc. * If there is no opening balance transaction, QString() will be returned. * * @param acc reference to account for which the opening balance transaction * should be retrieved * @return QString id for the transaction, or QString() if no transaction exists */ QString openingBalanceTransaction(const MyMoneyAccount& acc) const; /** * This method returns an indicator if the MyMoneyFile object has been * changed after it has last been saved to permanent storage. * * @return true if changed, false if not */ bool dirty() const; /** * This method is used to force the attached storage object to * be dirty. This is used by the application to re-set the dirty * flag after a failed upload to a server when the save operation * to a local temp file was OK. */ void setDirty() const; /** * Adds an institution to the file-global institution pool. A * respective institution-ID will be generated for this object. * The ID is stored as QString in the object passed as argument. * * An exception will be thrown upon error conditions. * @param institution The complete institution information in a * MyMoneyInstitution object */ void addInstitution(MyMoneyInstitution& institution); /** * Modifies an already existing institution in the file global * institution pool. * * An exception will be thrown upon error conditions. * * @param institution The complete new institution information */ void modifyInstitution(const MyMoneyInstitution& institution); /** * Deletes an existing institution from the file global institution pool * Also modifies the accounts that reference this institution as * their institution. * * An exception will be thrown upon error conditions. * * @param institution institution to be deleted. */ void removeInstitution(const MyMoneyInstitution& institution); void createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal); /** * Adds an account to the file-global account pool. A respective * account-ID will be generated within this record. The modified * members of @a account will be updated. * * A few parameters of the account to be added are checked against * the following conditions. If they do not match, an exception is * thrown. * * An account must match the following conditions: * * a) the account must have a name with length > 0 * b) the account must not have an id assigned * c) the transaction list must be empty * d) the account must not have any sub-ordinate accounts * e) the account must have no parent account * f) the account must not have any reference to a MyMoneyFile object * * An exception will be thrown upon error conditions. * * @param account The complete account information in a MyMoneyAccount object * @param parent The complete account information of the parent account */ void addAccount(MyMoneyAccount& account, MyMoneyAccount& parent); /** * Modifies an already existing account in the file global account pool. * * An exception will be thrown upon error conditions. * * @param account reference to the new account information */ void modifyAccount(const MyMoneyAccount& account); /** * This method re-parents an existing account * * An exception will be thrown upon error conditions. * * @param account MyMoneyAccount reference to account to be re-parented * @param parent MyMoneyAccount reference to new parent account */ void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent); /** * moves splits from one account to another * * @param oldAccount id of the current account * @param newAccount if of the new account * * @return the number of modified splits */ unsigned int moveSplits(const QString& oldAccount, const QString& newAccount); /** * This method is used to determince, if the account with the * given ID is referenced by any split in m_transactionList. * * @param id id of the account to be checked for * @return true if account is referenced, false otherwise */ bool hasActiveSplits(const QString& id) const; /** * This method is used to check whether a given * account id references one of the standard accounts or not. * * An exception will be thrown upon error conditions. * * @param id account id * @return true if account-id is one of the standards, false otherwise */ bool isStandardAccount(const QString& id) const; /** * Returns @a true, if transaction @p t is a transfer transaction. * A transfer transaction has two splits, both referencing either * an asset, a liability or an equity account. */ bool isTransfer(const MyMoneyTransaction& t) const; /** * This method is used to set the name for the specified standard account * within the storage area. An exception will be thrown, if an error * occurs * * @param id QString reference to one of the standard accounts. * @param name QString reference to the name to be set * */ void setAccountName(const QString& id, const QString& name) const; /** * Deletes an existing account from the file global account pool * This method only allows to remove accounts that are not * referenced by any split. Use moveSplits() to move splits * to another account. An exception is thrown in case of a * problem. * * @param account reference to the account to be deleted. */ void removeAccount(const MyMoneyAccount& account); /** * Deletes existing accounts and their subaccounts recursivly * from the global account pool. * This method expects that all accounts and their subaccounts * are no longer assigned to any transactions or splits. * An exception is thrown in case of a problem deleting an account. * * The optional parameter level is used to keep track of the recursion level. * If the recursion level exceeds 100 (some arbitrary number which seems a good * maximum), an exception is thrown. * * @param account_list Reference to a list of account IDs to be deleted. * @param level Parameter to keep track of recursion level (do not pass a value here). */ void removeAccountList(const QStringList& account_list, unsigned int level = 0); /** * This member function checks all accounts identified by account_list * and their subaccounts whether they are assigned to transactions/splits or not. * The function calls itself recursively with the list of sub-accounts of * the currently processed account. * * The optional parameter level is used to keep track of the recursion level. * If the recursion level exceeds 100 (some arbitrary number which seems a good * maximum), an exception is thrown. * * @param account_list A QStringList with account IDs that need to be checked. * @param level (optional) Optional parameter to indicate recursion level. * @return Returns 'false' if at least one account has been found that * is still referenced by a transaction. */ bool hasOnlyUnusedAccounts(const QStringList& account_list, unsigned int level = 0); /** * Adds a transaction to the file-global transaction pool. A respective * transaction-ID will be generated for this object. The ID is stored * as QString in the object passed as argument. * Splits must reference valid accounts and valid payees. The payee * id can be empty. * * An exception will be thrown upon error conditions. * * @param transaction reference to the transaction */ void addTransaction(MyMoneyTransaction& transaction); /** * This method is used to update a specific transaction in the * transaction pool of the MyMoneyFile object. * Splits must reference valid accounts and valid payees. The payee * id can be empty. * * An exception will be thrown upon error conditions. * * @param transaction reference to transaction to be changed */ void modifyTransaction(const MyMoneyTransaction& transaction); /** * This method is used to extract a transaction from the file global * transaction pool through an id. In case of an invalid id, an * exception will be thrown. * * @param id id of transaction as QString. * @return reference to the requested transaction */ MyMoneyTransaction transaction(const QString& id) const; /** * This method is used to extract a transaction from the file global * transaction pool through an index into an account. * * @param account id of the account as QString * @param idx number of transaction in this account * @return reference to MyMoneyTransaction object */ MyMoneyTransaction transaction(const QString& account, const int idx) const; /** * This method is used to pull a list of transactions from the file * global transaction pool. It returns all those transactions * that match the filter passed as argument. If the filter is empty, * the whole journal will be returned. * The list returned is sorted according to the transactions posting date. * If more than one transaction exists for the same date, the order among * them is undefined. * * @param filter MyMoneyTransactionFilter object with the match criteria * * @return set of transactions in form of a QList */ QList transactionList(MyMoneyTransactionFilter& filter) const; void transactionList(QList& list, MyMoneyTransactionFilter& filter) const; void transactionList(QList >& list, MyMoneyTransactionFilter& filter) const; /** * This method is used to remove a transaction from the transaction * pool (journal). * * @param transaction const reference to transaction to be deleted */ void removeTransaction(const MyMoneyTransaction& transaction); /** * This method is used to return the actual balance of an account * without it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date (default = QDate()) * @return balance of the account as MyMoneyMoney object */ MyMoneyMoney balance(const QString& id, const QDate& date) const; MyMoneyMoney balance(const QString& id) const; /** * This method is used to return the cleared balance of an account * without it's sub-ordinate accounts for a specific date. All * recorded transactions are included in the balance. * This method is used by the reconcialition functionality * * @param id id of the account in question * @param date return cleared balance for specific date * @return balance of the account as MyMoneyMoney object */ MyMoneyMoney clearedBalance(const QString& id, const QDate& date) const; /** * This method is used to return the actual balance of an account * including it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date (default = QDate()) * @return balance of the account as MyMoneyMoney object */ MyMoneyMoney totalBalance(const QString& id, const QDate& date) const; MyMoneyMoney totalBalance(const QString& id) const; /** * This method returns the number of transactions currently known to file * in the range 0..MAXUINT * * @param account QString reference to account id. If account is empty + all transactions (the journal) will be counted. If account * is not empty it returns the number of transactions * that have splits in this account. * * @return number of transactions in journal/account */ unsigned int transactionCount(const QString& account) const; unsigned int transactionCount() const; /** * This method returns a QMap filled with the number of transactions * per account. The account id serves as index into the map. If one * needs to have all transactionCounts() for many accounts, this method * is faster than calling transactionCount(const QString& account) many * times. * * @return QMap with numbers of transactions per account */ QMap transactionCountMap() const; /** * This method returns the number of institutions currently known to file * in the range 0..MAXUINT * * @return number of institutions known to file */ unsigned int institutionCount() const; /** * This method returns the number of accounts currently known to file * in the range 0..MAXUINT * * @return number of accounts currently known inside a MyMoneyFile object */ unsigned int accountCount() const; /** * Returns the institution of a given ID * * @param id id of the institution to locate * @return MyMoneyInstitution object filled with data. If the institution * could not be found, an exception will be thrown */ MyMoneyInstitution institution(const QString& id) const; /** * This method returns a list of the institutions * inside a MyMoneyFile object * * @param list reference to the list. It will be cleared by this method first */ void institutionList(QList& list) const; /** * This method returns a list of the institutions * inside a MyMoneyFile object. This is a convenience method * to the one above * * @return QList containing the institution objects */ QList institutionList() const; /** * Returns the account addressed by its id. * * @param id id of the account to locate. * @return MyMoneyAccount object carrying the @p id. An exception is thrown * if the id is unknown */ MyMoneyAccount account(const QString& id) const; /** * Returns the account addressed by its name. * * @param name name of the account to locate. * @return First MyMoneyAccount object found carrying the @p name. * An empty MyMoneyAccount object will be returned if the name is not found. */ MyMoneyAccount accountByName(const QString& name) const; /** * Returns the sub-account addressed by its name. * * @param acc account to search in * @param name name of the account to locate. * @return First MyMoneyAccount object found carrying the @p name. * An empty MyMoneyAccount object will be returned if the name is not found. */ MyMoneyAccount subAccountByName(const MyMoneyAccount& acc, const QString& name) const; /** * This method returns a list of accounts inside a MyMoneyFile object. * An optional parameter is a list of id's. If this list is emtpy (the default) * the returned list contains all accounts, otherwise only those referenced * in the id-list. * * @param list reference to QList receiving the account objects * @param idlist QStringList of account ids of those accounts that * should be returned. If this list is empty, all accounts * currently known will be returned. * * @param recursive if @p true, then recurse in all found accounts. The default is @p false */ void accountList(QList& list, const QStringList& idlist = QStringList(), const bool recursive = false) const; /** * This method is used to convert an account id to a string representation * of the names which can be used as a category description. If the account * is part of a hierarchy, the category name will be the concatenation of * the single account names separated by MyMoneyFile::AccountSeparator. * * @param accountId QString reference of the account's id * @param includeStandardAccounts if true, the standard top account will be part * of the name, otherwise it will not be included (default is @c false) * * @return QString of the constructed name. */ QString accountToCategory(const QString& accountId, bool includeStandardAccounts = false) const; /** * This method is used to convert a string representing a category to * an account id. A category can be the concatenation of multiple accounts * representing a hierarchy of accounts. They have to be separated by * MyMoneyFile::AccountSeparator. * * @param category const reference to QString containing the category * @param type account type if a specific type is required (defaults to Unknown) * * @return QString of the corresponding account. If account was not found * the return value will be an empty string. */ QString categoryToAccount(const QString& category, eMyMoney::Account::Type type) const; QString categoryToAccount(const QString& category) const; /** * This method is used to convert a string representing an asset or * liability account to an account id. An account name can be the * concatenation of multiple accounts representing a hierarchy of * accounts. They have to be separated by MyMoneyFile::AccountSeparator. * * @param name const reference to QString containing the account name * * @return QString of the corresponding account. If account was not found * the return value will be an empty string. */ QString nameToAccount(const QString& name) const; /** * This method is used to extract the parent part of an account hierarchy * name who's parts are separated by MyMoneyFile::AccountSeparator. * * @param name full account name * @return parent name (full account name excluding the last part) */ QString parentName(const QString& name) const; /** * This method is used to create a new payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void addPayee(MyMoneyPayee& payee); /** * This method is used to retrieve information about a payee * An exception will be thrown upon error conditions. * * @param id QString reference to id of payee * * @return MyMoneyPayee object of payee */ MyMoneyPayee payee(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a payee/receiver. * An exception will be thrown upon error conditions. * * @param payee QString reference to name of payee * * @return MyMoneyPayee object of payee */ MyMoneyPayee payeeByName(const QString& payee) const; /** * This method is used to modify an existing payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void modifyPayee(const MyMoneyPayee& payee); /** * This method is used to remove an existing payee. * An error condition occurs, if the payee is still referenced * by a split. * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ void removePayee(const MyMoneyPayee& payee); /** * This method returns a list of the payees * inside a MyMoneyStorage object * * @return QList containing the payee information */ QList payeeList() const; /** * This method is used to create a new tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void addTag(MyMoneyTag& tag); /** * This method is used to retrieve information about a tag * An exception will be thrown upon error conditions. * * @param id QString reference to id of tag * * @return MyMoneyTag object of tag */ MyMoneyTag tag(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a tag. * An exception will be thrown upon error conditions. * * @param tag QString reference to name of tag * * @return MyMoneyTag object of tag */ MyMoneyTag tagByName(const QString& tag) const; /** * This method is used to modify an existing tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void modifyTag(const MyMoneyTag& tag); /** * This method is used to remove an existing tag. * An error condition occurs, if the tag is still referenced * by a split. * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ void removeTag(const MyMoneyTag& tag); /** * This method returns a list of the tags * inside a MyMoneyStorage object * * @return QList containing the tag information */ QList tagList() const; /** * This method returns a list of the cost centers * inside a MyMoneyStorage object * * @return QList containing the cost center information */ void costCenterList(QList< MyMoneyCostCenter >& list) const; /** * This method is used to extract a value from the storage's * KeyValueContainer. For details see MyMoneyKeyValueContainer::value(). * @note Do not use this method to return the value of the key @p kmm-id. Use * storageId() instead. * * @param key const reference to QString containing the key * @return QString containing the value */ QString value(const QString& key) const; /** * This method is used to set a value in the storage's * KeyValueContainer. For details see MyMoneyKeyValueContainer::setValue(). * * @param key const reference to QString containing the key * @param val const reference to QString containing the value * * @note Keys starting with the leadin @p kmm- are reserved for internal use * by the MyMoneyFile object. */ void setValue(const QString& key, const QString& val); /** * This method returns the unique id of the attached storage object. * In case the storage object does not have an id yet, a new one will be * assigned. * * @return QString containing the value * * An exception is thrown if no storage object is attached. */ QString storageId(); /** * This method is used to delete a key-value-pair from the * storage's KeyValueContainer identified by the parameter * @p key. For details see MyMoneyKeyValueContainer::deletePair(). * * @param key const reference to QString containing the key */ void deletePair(const QString& key); /** * This method is used to add a scheduled transaction to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param sched reference to the MyMoneySchedule object */ void addSchedule(MyMoneySchedule& sched); /** * This method is used to modify an existing MyMoneySchedule * object. Therefor, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ void modifySchedule(const MyMoneySchedule& sched); /** * This method is used to remove an existing MyMoneySchedule object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ void removeSchedule(const MyMoneySchedule& sched); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ MyMoneySchedule schedule(const QString& id) const; /** * This method is used to extract a list of scheduled transactions * according to the filter criteria passed as arguments. * * @param accountId only search for scheduled transactions that reference * account @p accountId. If accountId is the empty string, * this filter is off. Default is @p QString(). * @param type only schedules of type @p type are searched for. * See eMyMoney::Schedule::Type for details. * Default is eMyMoney::Schedule::Type::Any * @param occurrence only schedules of occurrence type @p occurrence are searched for. * See eMyMoney::Schedule::Occurrence for details. * Default is eMyMoney::Schedule::Occurrence::Any * @param paymentType only schedules of payment method @p paymentType * are searched for. * See eMyMoney::Schedule::PaymentType for details. * Default is eMyMoney::Schedule::PaymentType::Any * @param startDate only schedules with payment dates after @p startDate * are searched for. Default is all dates (QDate()). * @param endDate only schedules with payment dates ending prior to @p endDate * are searched for. Default is all dates (QDate()). * @param overdue if true, only those schedules that are overdue are * searched for. Default is false (all schedules will be returned). * * @return const QList list of schedule objects. */ QList scheduleList(const QString& accountId, const eMyMoney::Schedule::Type type, const eMyMoney::Schedule::Occurrence occurrence, const eMyMoney::Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, const bool overdue) const; QList scheduleList(const QString& accountId) const; QList scheduleList() const; QStringList consistencyCheck(); /** * MyMoneyFile::openingBalancesPrefix() is a special string used * to generate the name for opening balances accounts. See openingBalanceAccount() * for details. */ static QString openingBalancesPrefix(); /** * MyMoneyFile::AccountSeparator is used as the separator * between account names to form a hierarchy. */ static const QString AccountSeparator; /** * createCategory creates a category from a text name. * * The whole account hierarchy is created if it does not * already exist. e.g if name = Bills:Credit Card and * base = expense(), Bills will first be checked to see if * it exists and created if not. Credit Card will then * be created with Bills as it's parent. The Credit Card account * will have it's id returned. * * @param base The base account (expense or income) * @param name The category to create * * @return The category account id or empty on error. * * @exception An exception will be thrown, if @p base is not equal * expense() or income(). **/ QString createCategory(const MyMoneyAccount& base, const QString& name); /** * This method is used to get the account id of the split for * a transaction from the text found in the QIF $ or L record. * If an account with the name is not found, the user is asked * if it should be created. * * @param name name of account as found in the QIF file * @param value value found in the T record * @param value2 value found in the $ record for split transactions * * @return id of the account for the split. If no name is specified * or the account was not found and not created the * return value will be "". */ QString checkCategory(const QString& name, const MyMoneyMoney& value, const MyMoneyMoney& value2); /** * This method is used to add a new security object to the engine. * The ID of the object is the trading symbol, so there is no need for an additional * ID since the symbol is guaranteed to be unique. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object */ void addSecurity(MyMoneySecurity& security); /** * This method is used to modify an existing MyMoneySchedule * object. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be updated */ void modifySecurity(const MyMoneySecurity& security); /** * This method is used to remove an existing MyMoneySecurity object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be removed */ void removeSecurity(const MyMoneySecurity& security); /** * This method is used to retrieve a single MyMoneySecurity object. * The id of the object must be supplied in the parameter @p id. * If no security with the given id is found, then a corresponding * currency is searched. If @p id is empty, the baseCurrency() is returned. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySecurity object * @return MyMoneySecurity object */ MyMoneySecurity security(const QString& id) const; /** * This method is used to retrieve a list of all MyMoneySecurity objects. */ QList securityList() const; /** * This method is used to add a new currency object to the engine. * The ID of the object is the trading symbol, so there is no need for an additional * ID since the symbol is guaranteed to be unique. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneySecurity object */ void addCurrency(const MyMoneySecurity& currency); /** * This method is used to modify an existing MyMoneySecurity * object. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneySecurity object */ void modifyCurrency(const MyMoneySecurity& currency); /** * This method is used to remove an existing MyMoneySecurity object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneySecurity object */ void removeCurrency(const MyMoneySecurity& currency); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * If @p id is empty, this method returns baseCurrency(). * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ MyMoneySecurity currency(const QString& id) const; /** * This method is used to retrieve the map of ancient currencies (together with their last prices) * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QMap of all MyMoneySecurity and MyMoneyPrice objects. */ QMap ancientCurrencies() const; /** * This method is used to retrieve the list of available currencies * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneySecurity objects. */ QList availableCurrencyList() const; /** * This method is used to retrieve the list of stored currencies * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneySecurity objects. */ QList currencyList() const; /** * This method retrieves a MyMoneySecurity object representing * the selected base currency. If the base currency is not * selected (e.g. due to a previous call to setBaseCurrency()) * a standard MyMoneySecurity object will be returned. See * MyMoneySecurity() for details. * * An exception will be thrown upon erroneous situations. * * @return MyMoneySecurity describing base currency */ MyMoneySecurity baseCurrency() const; /** * This method returns the foreign currency of the given two * currency ids. If second is the base currency id then @a first * is returned otherwise @a second is returned. */ QString foreignCurrency(const QString& first, const QString& second) const; /** * This method allows to select the base currency. It does * not perform any changes to the data in the engine. It merely * stores a reference to the base currency. The currency * passed as argument must exist in the engine. * * An exception will be thrown upon erroneous situations. * * @param currency */ void setBaseCurrency(const MyMoneySecurity& currency); /** * This method adds/replaces a price to/from the price list */ void addPrice(const MyMoneyPrice& price); /** * This method removes a price from the price list */ void removePrice(const MyMoneyPrice& price); /** * This method is used to retrieve a price for a specific security * on a specific date. If there is no price for this date, the last * known price for this currency is used. If no price information * is available, 1.0 will be returned as price. * * @param fromId the id of the currency in question * @param toId the id of the currency to convert to (if emtpy, baseCurrency) * @param date the date for which the price should be returned (default = today) * @param exactDate if true, entry for date must exist, if false any price information * with a date less or equal to @p date will be returned * * @return price found as MyMoneyPrice object * @note This throws an exception when the base currency is not set and toId is empty */ MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& date, const bool exactDate = false) const; MyMoneyPrice price(const QString& fromId, const QString& toId) const; MyMoneyPrice price(const QString& fromId) const; /** * This method returns a list of all prices. * * @return MyMoneyPriceList of all MyMoneyPrice objects. */ MyMoneyPriceList priceList() const; /** * This method allows to interrogate the engine, if a known account * with id @p id has a subaccount with the name @p name. * * @param id id of the account to look at * @param name account name that needs to be searched force * @retval true account with name @p name found as subaccounts * @retval false no subaccount present with that name */ bool hasAccount(const QString& id, const QString& name) const; /** * This method is used to retrieve the list of all reports * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyReport objects. */ QList reportList() const; /** * Adds a report to the file-global institution pool. A * respective report-ID will be generated for this object. * The ID is stored as QString in the object passed as argument. * * An exception will be thrown upon error conditions. * * @param report The complete report information in a * MyMoneyReport object */ void addReport(MyMoneyReport& report); /** * Modifies an already existing report in the file global * report pool. * * An exception will be thrown upon error conditions. * * @param report The complete new report information */ void modifyReport(const MyMoneyReport& report); /** * This method returns the number of reports currently known to file * in the range 0..MAXUINT * * @return number of reports known to file */ unsigned countReports() const; /** * This method is used to retrieve a single MyMoneyReport object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyReport object * @return MyMoneyReport object */ MyMoneyReport report(const QString& id) const; /** * This method is used to remove an existing MyMoneyReport object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param report const reference to the MyMoneyReport object to be updated */ void removeReport(const MyMoneyReport& report); /** * This method is used to retrieve the list of all budgets * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyBudget objects. */ QList budgetList() const; /** * Adds a budget to the file-global institution pool. A * respective budget-ID will be generated for this object. * The ID is stored as QString in the object passed as argument. * * An exception will be thrown upon error conditions. * * @param budget The complete budget information in a * MyMoneyBudget object */ void addBudget(MyMoneyBudget &budget); /** * This method is used to retrieve the id to a corresponding * name of a budget. * An exception will be thrown upon error conditions. * * @param budget QString reference to name of budget * * @return MyMoneyBudget refernce to object of budget */ MyMoneyBudget budgetByName(const QString& budget) const; /** * Modifies an already existing budget in the file global * budget pool. * * An exception will be thrown upon error conditions. * * @param budget The complete new budget information */ void modifyBudget(const MyMoneyBudget& budget); /** * This method returns the number of budgets currently known to file * in the range 0..MAXUINT * * @return number of budgets known to file */ unsigned countBudgets() const; /** * This method is used to retrieve a single MyMoneyBudget object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyBudget object * @return MyMoneyBudget object */ MyMoneyBudget budget(const QString& id) const; /** * This method is used to remove an existing MyMoneyBudget object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param budget const reference to the MyMoneyBudget object to be updated */ void removeBudget(const MyMoneyBudget& budget); /** * This method is used to add a VAT split to a transaction. * * @param transaction reference to the transaction * @param account reference to the account * @param category reference to the category * @param amount reference to the amount of the VAT split * * @return true if a VAT split has been added */ bool addVATSplit(MyMoneyTransaction& transaction, const MyMoneyAccount& account, const MyMoneyAccount& category, const MyMoneyMoney& amount); /** * This method checks, if the given @p object is referenced * by another engine object. * * @param obj const reference to object to be checked * @param skipCheck QBitArray with eStorage::Reference bits set for which * the check should be skipped * * @retval false @p object is not referenced * @retval true @p institution is referenced */ bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const; bool isReferenced(const MyMoneyObject& obj) const; /** * Returns true if any of the accounts referenced by the splits * of transaction @a t is closed. */ bool referencesClosedAccount(const MyMoneyTransaction& t) const; /** * Returns true if the accounts referenced by the split @a s is closed. */ bool referencesClosedAccount(const MyMoneySplit& s) const; /** * This method checks if the given check no &p no is used in * a transaction referencing account &p accId. If @p accId is empty, * @p false is returned. * * @param accId id of account to checked * @param no check number to be verified if used or not * @retval false @p no is not in use * @retval true @p no is already assigned */ bool checkNoUsed(const QString& accId, const QString& no) const; /** * This method returns the highest assigned check no for * account @p accId. * * @param accId id of account to be scanned * @return highest check no. used */ QString highestCheckNo(const QString& accId) const; /** * This method checks if there is a transaction * after the date @p date for account @p accId. * * @param accId id of account to be scanned * @param date date to compare with * @retval false if there is no transaction after @p date * @retval true if there is a transaction after @p date */ bool hasNewerTransaction(const QString& accId, const QDate& date) const; /** * Clear all internal caches (used internally for performance measurements) */ void clearCache(); void forceDataChanged(); void preloadCache(); /** * This returns @p true if file and online balance of a specific * @p account are matching. Returns false if there is no online balance. * * @param account @p account to be checked * @retval false if @p account has balance mismatch or if there is no online balance. * @retval true if @p account has matching balances */ bool hasMatchingOnlineBalance(const MyMoneyAccount& account) const; /** * This returns the number of transactions of a specific reconciliation state @p state of account with id @p accId. * * @param accId @p accId is the account id of account to be checked * @param state @p state reconciliation state * @return number of transactions with state @p state */ int countTransactionsWithSpecificReconciliationState(const QString& accId, eMyMoney::TransactionFilter::State state) const; /** * @brief Saves a new onlineJob * @param job you stay owner of the object (a copy will be created) */ void addOnlineJob(onlineJob& job); /** * @brief Saves a onlineJob * @param job you stay owner of the object (a copy will be created) */ void modifyOnlineJob(const onlineJob job); /** * @brief Returns onlineJob identified by jobId * @param jobId * @return */ onlineJob getOnlineJob(const QString &jobId) const; /** * @brief Returns all onlineJobs * @return all online jobs, caller gains ownership */ QList onlineJobList() const; /** * @brief Returns the number of onlineJobs */ int countOnlineJobs() const; /** * @brief Remove onlineJob * * @note Removing an onlineJob fails if it is locked */ void removeOnlineJob(const onlineJob& job); /** * @brief Removes multiple onlineJobs by id * * @note Removing an onlineJob fails if it is locked */ void removeOnlineJob(const QStringList onlineJobIds); protected: /** * This is the constructor for a new empty file description */ MyMoneyFile(); Q_SIGNALS: /** * This signal is emitted when a transaction has been committed and * the notifications are about to be sent out. */ void beginChangeNotification(); /** * This signal is emitted when a transaction has been committed and * all notifications have been sent out. */ void endChangeNotification(); /** * This signal is emitted whenever any data has been changed in the engine * via any of the methods of this object */ void dataChanged(); /** * This signal is emitted by the engine whenever a new object * had been added. The data for the new object is contained in * @a obj. */ void objectAdded(eMyMoney::File::Object objType, const MyMoneyObject * const obj); /** * This signal is emitted by the engine whenever an object * had been removed. * * @note: The data contained in @a obj is only for reference * purposes and should not be used to call any MyMoneyFile * method anymore as the object is already deleted in the storage * when the signal is emitted. */ void objectRemoved(eMyMoney::File::Object objType, const QString& id); /** * This signal is emitted by the engine whenever an object * had been changed. The new state of the object is contained * in @a obj. */ void objectModified(eMyMoney::File::Object objType, const MyMoneyObject * const obj); /** * This signal is emitted by the engine whenever the balance * of an account had been changed by adding, modifying or * removing transactions from the MyMoneyFile object. */ void balanceChanged(const MyMoneyAccount& acc); /** * This signal is emitted by the engine whenever the value * of an account had been changed by adding or removing * prices from the MyMoneyFile object. */ void valueChanged(const MyMoneyAccount& acc); private: static MyMoneyFile file; MyMoneyFile& operator=(MyMoneyFile&); // not allowed for singleton MyMoneyFile(const MyMoneyFile&); // not allowed for singleton QString locateSubAccount(const MyMoneyAccount& base, const QString& category) const; void ensureDefaultCurrency(MyMoneyAccount& acc) const; void warningMissingRate(const QString& fromId, const QString& toId) const; /** * This method creates an opening balances account. The name is constructed * using MyMoneyFile::openingBalancesPrefix() and appending " (xxx)" in * case the @p security is not the baseCurrency(). The account created * will be a sub-account of the standard equity account provided by equity(). * * @param security Security for which the account is searched */ MyMoneyAccount createOpeningBalanceAccount(const MyMoneySecurity& security); MyMoneyAccount openingBalanceAccount_internal(const MyMoneySecurity& security) const; /** * Make sure that the splits value has the precision of the corresponding account */ void fixSplitPrecision(MyMoneyTransaction& t) const; private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; }; class MyMoneyFileTransactionPrivate; class KMM_MYMONEY_EXPORT MyMoneyFileTransaction { Q_DISABLE_COPY(MyMoneyFileTransaction) public: MyMoneyFileTransaction(); ~MyMoneyFileTransaction(); /** * Commit the current transaction. * * @warning Make sure not to use any variable that might have been altered by * the transaction. Please keep in mind, that changing transactions * can also affect account objects. If you still need those variables * just reload them from the engine. */ void commit(); void rollback(); void restart(); private: MyMoneyFileTransactionPrivate * const d_ptr; Q_DECLARE_PRIVATE(MyMoneyFileTransaction) }; #endif diff --git a/kmymoney/mymoney/mymoneyobjectcontainer.cpp b/kmymoney/mymoney/mymoneyobjectcontainer.cpp index 882ca5b54..81af86ec7 100644 --- a/kmymoney/mymoney/mymoneyobjectcontainer.cpp +++ b/kmymoney/mymoney/mymoneyobjectcontainer.cpp @@ -1,397 +1,397 @@ /*************************************************************************** mymoneyobjectcontainer.cpp ------------------- copyright : (C) 2006 by Thomas Baumagrt email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyobjectcontainer.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/onlinejob.h" -#include "imymoneystorage.h" +#include "mymoneystoragemgr.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyschedule.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneycostcenter.h" struct MyMoneyObjectContainer::Private { Private(MyMoneyObjectContainer *p) : storage(0), pub(p) { } void assignFraction(MyMoneyAccount* acc) { if (acc != 0 && acc->fraction() == -1) { const MyMoneySecurity& sec = pub->security(acc->currencyId()); acc->fraction(sec); } } template const MyMoneyObject * getObject(const CacheType& map, const QString& id) { typename CacheType::const_iterator it = map.find(id); if (it != map.end()) { return (*it); } return 0; } template bool refreshObject(const QString& id, QHash &hash, ObjFactory f) { typename QHash::iterator it = hash.find(id); if (it != hash.end()) { delete *it; const ObjType& t = (storage->*f)(id); *it = new ObjType(t); return true; } return false; } template bool clearObject(CacheType& map, const QString& id) { typename CacheType::iterator it = map.find(id); if (it != map.end()) { delete(*it); map.erase(it); return true; } return false; } template void clearCache(CacheType& map) { // delete all objects qDeleteAll(map); // then delete the pointers to them map.clear(); } template void listMethodImpl(QList& list, const QHash &hash) { for (typename QHash::const_iterator it = hash.begin(); it != hash.end(); ++it) { list.append(*it.value()); } } template void preloadMethodImpl(const ObjType& obj, QHash &hash) { const QString &objId = obj.id(); typename QHash::iterator it = hash.find(objId); if (it != hash.end()) { delete hash.value(objId); } hash[objId] = new ObjType(obj); } template void preloadListMethodImpl(const QList& list, QHash &hash) { for (typename QList::const_iterator it = list.begin(); it != list.end(); ++it) preloadMethodImpl(*it, hash); } template const ObjType& objectAccessMethodImpl(const QString& id, QHash &hash, ObjFactory f) { static ObjType nullElement; if (id.isEmpty()) return nullElement; typename QHash::const_iterator it = hash.constFind(id); if (it == hash.constEnd()) { /* not found, need to load from engine */ const ObjType &x = (storage->*f)(id); hash[id] = new ObjType(x); return *hash.value(id); } return *(*it); } QHash accountCache; QHash payeeCache; QHash tagCache; QHash securityCache; QHash institutionCache; QHash scheduleCache; QHash onlineJobCache; QHash costCenterCache; - IMyMoneyStorage* storage; + MyMoneyStorageMgr* storage; MyMoneyObjectContainer *pub; }; MyMoneyObjectContainer::MyMoneyObjectContainer() : d(new Private(this)) { } MyMoneyObjectContainer::~MyMoneyObjectContainer() { clear(); delete d; } -void MyMoneyObjectContainer::clear(IMyMoneyStorage* storage) +void MyMoneyObjectContainer::clear(MyMoneyStorageMgr* storage) { d->clearCache(d->accountCache); d->clearCache(d->payeeCache); d->clearCache(d->tagCache); d->clearCache(d->securityCache); d->clearCache(d->institutionCache); d->clearCache(d->scheduleCache); d->clearCache(d->onlineJobCache); d->clearCache(d->costCenterCache); if (storage) d->storage = storage; } void MyMoneyObjectContainer::clear(const QString& id) { if (d->clearObject(d->accountCache, id)) return; if (d->clearObject(d->payeeCache, id)) return; if (d->clearObject(d->tagCache, id)) return; if (d->clearObject(d->securityCache, id)) return; if (d->clearObject(d->institutionCache, id)) return; if (d->clearObject(d->scheduleCache, id)) return; if (d->clearObject(d->onlineJobCache, id)) return; if (d->clearObject(d->costCenterCache, id)) return; qWarning("Ooops, should clear an unknown object with id '%s'", qPrintable(id)); } const MyMoneyObject * MyMoneyObjectContainer::object(const QString& id) const { const MyMoneyObject * obj = 0; if ((obj = d->getObject(d->accountCache, id))) return obj; if ((obj = d->getObject(d->payeeCache, id))) return obj; if ((obj = d->getObject(d->tagCache, id))) return obj; if ((obj = d->getObject(d->securityCache, id))) return obj; if ((obj = d->getObject(d->institutionCache, id))) return obj; if ((obj = d->getObject(d->scheduleCache, id))) return obj; if ((obj = d->getObject(d->onlineJobCache, id))) return obj; if ((obj = d->getObject(d->costCenterCache, id))) return obj; qWarning("Ooops, should get an unknown object with id '%s'", qPrintable(id)); return 0; } void MyMoneyObjectContainer::account(QList& list) { QHash::const_iterator it; for (it = d->accountCache.constBegin(); it != d->accountCache.constEnd(); ++it) { const MyMoneyAccount* node = *it; if (node) { d->assignFraction(const_cast(node)); list.append(*node); } } } MyMoneyAccount MyMoneyObjectContainer::account(const QString& id) { static MyMoneyAccount nullElement; auto acc = d->accountCache.value(id, &nullElement); if (id.isEmpty()) { return *acc; } else if (acc->id().isEmpty()) { /* not found, need to load from engine */ acc = new MyMoneyAccount(d->storage->account(id)); d->accountCache.insert(id, acc); } d->assignFraction(const_cast(acc)); return *acc; } MyMoneyAccount MyMoneyObjectContainer::accountByName(const QString& name) const { static MyMoneyAccount nullElement; QHash::const_iterator it; for (it = d->accountCache.constBegin(); it != d->accountCache.constEnd(); ++it) { const MyMoneyAccount* node = *it; if (node && node->name() == name) { return **it; } } return nullElement; } void MyMoneyObjectContainer::refresh(const QString& id) { if (id.isEmpty()) return; - if (d->refreshObject(id, d->accountCache, &IMyMoneyStorage::account)) + if (d->refreshObject(id, d->accountCache, &MyMoneyStorageMgr::account)) return; - if (d->refreshObject(id, d->payeeCache, &IMyMoneyStorage::payee)) + if (d->refreshObject(id, d->payeeCache, &MyMoneyStorageMgr::payee)) return; - if (d->refreshObject(id, d->tagCache, &IMyMoneyStorage::tag)) + if (d->refreshObject(id, d->tagCache, &MyMoneyStorageMgr::tag)) return; - if (d->refreshObject(id, d->institutionCache, &IMyMoneyStorage::institution)) + if (d->refreshObject(id, d->institutionCache, &MyMoneyStorageMgr::institution)) return; - if (d->refreshObject(id, d->scheduleCache, &IMyMoneyStorage::schedule)) + if (d->refreshObject(id, d->scheduleCache, &MyMoneyStorageMgr::schedule)) return; - if (d->refreshObject(id, d->onlineJobCache, &IMyMoneyStorage::getOnlineJob)) + if (d->refreshObject(id, d->onlineJobCache, &MyMoneyStorageMgr::getOnlineJob)) return; - if (d->refreshObject(id, d->costCenterCache, &IMyMoneyStorage::costCenter)) + if (d->refreshObject(id, d->costCenterCache, &MyMoneyStorageMgr::costCenter)) return; // special handling of securities QHash::iterator it = d->securityCache.find(id); if (it != d->securityCache.end()) { delete *it; const MyMoneySecurity& s = d->storage->security(id); if (s.id().isEmpty()) { const MyMoneySecurity& c = d->storage->currency(id); *it = new MyMoneySecurity(c); } else { *it = new MyMoneySecurity(s); } return; } qWarning("Ooops, should refresh an unknown object with id '%s'", qPrintable(id)); } MyMoneyPayee MyMoneyObjectContainer::payee(const QString& id) { - return d->objectAccessMethodImpl(id, d->payeeCache, &IMyMoneyStorage::payee); + return d->objectAccessMethodImpl(id, d->payeeCache, &MyMoneyStorageMgr::payee); } MyMoneyTag MyMoneyObjectContainer::tag(const QString& id) { - return d->objectAccessMethodImpl(id, d->tagCache, &IMyMoneyStorage::tag); + return d->objectAccessMethodImpl(id, d->tagCache, &MyMoneyStorageMgr::tag); } MyMoneySecurity MyMoneyObjectContainer::security(const QString& id) { - return d->objectAccessMethodImpl(id, d->securityCache, &IMyMoneyStorage::security); + return d->objectAccessMethodImpl(id, d->securityCache, &MyMoneyStorageMgr::security); } MyMoneyInstitution MyMoneyObjectContainer::institution(const QString& id) { - return d->objectAccessMethodImpl(id, d->institutionCache, &IMyMoneyStorage::institution); + return d->objectAccessMethodImpl(id, d->institutionCache, &MyMoneyStorageMgr::institution); } MyMoneySchedule MyMoneyObjectContainer::schedule(const QString& id) { - return d->objectAccessMethodImpl(id, d->scheduleCache, &IMyMoneyStorage::schedule); + return d->objectAccessMethodImpl(id, d->scheduleCache, &MyMoneyStorageMgr::schedule); } void MyMoneyObjectContainer::preloadAccount(const QList& list) { d->preloadListMethodImpl(list, d->accountCache); } void MyMoneyObjectContainer::preloadPayee(const QList& list) { d->preloadListMethodImpl(list, d->payeeCache); } void MyMoneyObjectContainer::preloadTag(const QList& list) { d->preloadListMethodImpl(list, d->tagCache); } void MyMoneyObjectContainer::preloadInstitution(const QList& list) { d->preloadListMethodImpl(list, d->institutionCache); } void MyMoneyObjectContainer::preloadSecurity(const QList& list) { d->preloadListMethodImpl(list, d->securityCache); } void MyMoneyObjectContainer::preloadSchedule(const QList& list) { d->preloadListMethodImpl(list, d->scheduleCache); } void MyMoneyObjectContainer::preloadOnlineJob(const QList< onlineJob >& list) { d->preloadListMethodImpl(list, d->onlineJobCache); } void MyMoneyObjectContainer::preloadAccount(const MyMoneyAccount& account) { d->preloadMethodImpl(account, d->accountCache); } void MyMoneyObjectContainer::preloadSecurity(const MyMoneySecurity& security) { d->preloadMethodImpl(security, d->securityCache); } void MyMoneyObjectContainer::preloadPayee(const MyMoneyPayee& payee) { d->preloadMethodImpl(payee, d->payeeCache); } void MyMoneyObjectContainer::preloadTag(const MyMoneyTag& tag) { d->preloadMethodImpl(tag, d->tagCache); } void MyMoneyObjectContainer::preloadInstitution(const MyMoneyInstitution& institution) { d->preloadMethodImpl(institution, d->institutionCache); } void MyMoneyObjectContainer::preloadSchedule(const MyMoneySchedule& schedule) { d->preloadMethodImpl(schedule, d->scheduleCache); } void MyMoneyObjectContainer::preloadOnlineJob(const onlineJob& job) { d->preloadMethodImpl(job, d->onlineJobCache); } void MyMoneyObjectContainer::payee(QList& list) { d->listMethodImpl(list, d->payeeCache); } void MyMoneyObjectContainer::tag(QList& list) { d->listMethodImpl(list, d->tagCache); } void MyMoneyObjectContainer::institution(QList& list) { d->listMethodImpl(list, d->institutionCache); } diff --git a/kmymoney/mymoney/mymoneyobjectcontainer.h b/kmymoney/mymoney/mymoneyobjectcontainer.h index 93e855411..06b674bc8 100644 --- a/kmymoney/mymoney/mymoneyobjectcontainer.h +++ b/kmymoney/mymoney/mymoneyobjectcontainer.h @@ -1,108 +1,108 @@ /*************************************************************************** mymoneyobjectcontainer.h ------------------- copyright : (C) 2006 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYOBJECTCONTAINER_H #define MYMONEYOBJECTCONTAINER_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmm_mymoney_export.h" /** * @author Thomas Baumgart */ /** * This class represents a generic container for all MyMoneyObject derived objects. */ -class IMyMoneyStorage; +class MyMoneyStorageMgr; class MyMoneyObject; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyPayee; class MyMoneyTag; class MyMoneySchedule; class MyMoneyCostCenter; class onlineJob; class KMM_MYMONEY_EXPORT MyMoneyObjectContainer : public QObject { Q_OBJECT public: MyMoneyObjectContainer(); ~MyMoneyObjectContainer(); MyMoneyAccount account(const QString& id); MyMoneyPayee payee(const QString& id); MyMoneyTag tag(const QString& id); MyMoneySecurity security(const QString& id); MyMoneyInstitution institution(const QString& id); MyMoneySchedule schedule(const QString& id); MyMoneyCostCenter costCenter(const QString& id); void account(QList& list); void payee(QList& list); void tag(QList& list); void institution(QList& list); void costCenter(QList& list); void preloadAccount(const QList& list); void preloadPayee(const QList& list); void preloadTag(const QList& list); void preloadInstitution(const QList& list); void preloadSecurity(const QList& list); void preloadSchedule(const QList& list); void preloadOnlineJob(const QList& list); void preloadCostCenter(const QList& list); void preloadAccount(const MyMoneyAccount& account); void preloadSecurity(const MyMoneySecurity& security); void preloadPayee(const MyMoneyPayee& payee); void preloadTag(const MyMoneyTag& tag); void preloadInstitution(const MyMoneyInstitution& institution); void preloadSchedule(const MyMoneySchedule& schedule); void preloadOnlineJob(const onlineJob& job); void clear(const QString& id); - void clear(IMyMoneyStorage* storage = 0); + void clear(MyMoneyStorageMgr* storage = 0); MyMoneyAccount accountByName(const QString& name) const; const MyMoneyObject * object(const QString& id) const; /** * This method refreshes an already existing object in the container * with a copy from the engine. The object is identified by its @a id. * If the object is unknown or the @a id is empty, nothing is done. */ void refresh(const QString& id); private: struct Private; Private * const d; }; #endif diff --git a/kmymoney/mymoney/storage/CMakeLists.txt b/kmymoney/mymoney/storage/CMakeLists.txt index e549fbf72..ada824609 100644 --- a/kmymoney/mymoney/storage/CMakeLists.txt +++ b/kmymoney/mymoney/storage/CMakeLists.txt @@ -1,46 +1,51 @@ if(BUILD_TESTING) add_subdirectory(tests) endif() set(storage_HEADERS - imymoneystorage.h imymoneyserialize.h imymoneystorageformat.h + imymoneystorageformat.h + mymoneystoragemgr.h kmymoneystorageplugin.h databasestoreableobject.h ) set(storage_a_SOURCES - imymoneystorageformat.cpp mymoneystoragexml.cpp mymoneystoragedump.cpp - mymoneyseqaccessmgr.cpp + imymoneystorageformat.cpp + mymoneystoragexml.cpp + mymoneystoragemgr.cpp mymoneystorageanon.cpp mymoneystoragesql.cpp - mymoneydatabasemgr.cpp mymoneydbdef.cpp mymoneydbdriver.cpp kmymoneystorageplugin.cpp mymoneystoragenames.cpp ) +if(KMM_DEBUG) + list(APPEND storage_a_SOURCES mymoneystoragedump.cpp) +endif() + # This library is actually not needed. It is built just for # convenience, and then linked into kmm_mymoney. add_library( kmm_storage STATIC ${storage_a_SOURCES} ) kde_target_enable_exceptions(kmm_storage PUBLIC) target_link_libraries( kmm_storage PUBLIC KF5::Service KF5::I18n Qt5::Core Qt5::Xml Qt5::Sql Qt5::Gui Alkimia::alkimia ) ########### install files ############### install(FILES ${storage_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel ) install(FILES kmymoney-sqlstorageplugin.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) diff --git a/kmymoney/mymoney/storage/imymoneyserialize.h b/kmymoney/mymoney/storage/imymoneyserialize.h deleted file mode 100644 index 04e7db605..000000000 --- a/kmymoney/mymoney/storage/imymoneyserialize.h +++ /dev/null @@ -1,435 +0,0 @@ -/*************************************************************************** - imymoneyserialize.h - description - ------------------- - begin : Fri May 10 2002 - copyright : (C) 2000-2002 by Michael Edwardes - email : mte@users.sourceforge.net - Javier Campos Morales - Felix Rodriguez - John C - Thomas Baumgart - Kevin Tambascio - (C) 2017 by Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef IMYMONEYSERIALIZE_H -#define IMYMONEYSERIALIZE_H - -// ---------------------------------------------------------------------------- -// QT Includes - -#include - -// ---------------------------------------------------------------------------- -// Project Includes - -class QUrl; -class QDate; -class QString; -class QStringList; - -class MyMoneyAccount; -class MyMoneyBudget; -class MyMoneyCostCenter; -class MyMoneyInstitution; -class MyMoneyReport; -class MyMoneySecurity; -class MyMoneyPayee; -class MyMoneyPrice; -class MyMoneySchedule; -class MyMoneyStorageSql; -class MyMoneyTag; -class MyMoneyPrice; -class MyMoneyTransaction; -class MyMoneyTransactionFilter; -class onlineJob; - -template class QExplicitlySharedDataPointer; -template class QMap; -template class QList; -template struct QPair; - -typedef QPair MyMoneySecurityPair; -typedef QMap MyMoneyPriceEntries; -typedef QMap MyMoneyPriceList; - -namespace eMyMoney { namespace Schedule { enum class Type; } } -namespace eMyMoney { namespace Schedule { enum class Occurrence; } } -namespace eMyMoney { namespace Schedule { enum class PaymentType; } } - -/** - * @author Thomas Baumgart - * @author Łukasz Wojniłowicz - */ - -/** - * This class represents the interface to serialize a MyMoneyStorage object - */ -class IMyMoneySerialize -{ -public: - virtual ~IMyMoneySerialize() = default; - - // general get functions - virtual MyMoneyPayee user() const = 0; - virtual QDate creationDate() const = 0; - virtual QDate lastModificationDate() const = 0; - virtual uint currentFixVersion() const = 0; - virtual uint fileFixVersion() const = 0; - - // general set functions - virtual void setUser(const MyMoneyPayee& val) = 0; - virtual void setCreationDate(const QDate& val) = 0; - virtual void setFileFixVersion(uint v) = 0; - /** - * This method is used to get a SQL reader for subsequent database access - */ - virtual QExplicitlySharedDataPointer connectToDatabase - (const QUrl &url) = 0; - /** - * This method is used when a database file is open, and the data is to - * be saved in a different file or format. It will ensure that all data - * from the database is available in memory to enable it to be written. - */ - virtual void fillStorage() = 0; - - /** - * This method is used to set the last modification date of - * the storage object. It also clears the dirty flag and should - * therefor be called as last operation when loading from a - * file. - * - * @param val QDate of last modification - */ - virtual void setLastModificationDate(const QDate& val) = 0; - - /** - * This method returns a list of accounts inside the storage object. - * - * @param list reference to QList receiving the account objects - * - * @note The standard accounts will not be returned - */ - virtual void accountList(QList& list) const = 0; - - /** - * This method returns a list of the institutions - * inside a MyMoneyStorage object - * - * @return QMap containing the institution information - */ - virtual QList institutionList() const = 0; - - /** - * This method is used to pull a list of transactions from the file - * global transaction pool. It returns all those transactions - * that match the filter passed as argument. If the filter is empty, - * the whole journal will be returned. - * - * @param list reference to QList receiving - * the set of transactions - * @param filter MyMoneyTransactionFilter object with the match criteria - */ - virtual void transactionList(QList& list, MyMoneyTransactionFilter& filter) const = 0; - - - /** - * This method returns whether a given transaction is already in memory, to avoid - * reloading it from the database - */ - virtual bool isDuplicateTransaction(const QString&) const = 0; - /** - * This method returns a list of the payees - * inside a MyMoneyStorage object - * - * @return QList containing the payee information - */ - virtual QList payeeList() const = 0; - - /** - * This method returns a list of the tags - * inside a MyMoneyStorage object - * - * @return QList containing the tag information - */ - virtual QList tagList() const = 0; - - /** - * This method returns a list of the scheduled transactions - * inside a MyMoneyStorage object. In order to retrieve a complete - * list of the transactions, all arguments should be used with their - * default arguments. - */ - virtual QList scheduleList(const QString&, - eMyMoney::Schedule::Type, - eMyMoney::Schedule::Occurrence, - eMyMoney::Schedule::PaymentType, - const QDate&, - const QDate&, - bool) const = 0; - - /** - * This method returns a list of security objects that the engine has - * knowledge of. - */ - virtual QList securityList() const = 0; - - /** - * This method returns a list of onlineJobs the engine has - */ - virtual QList onlineJobList() const = 0; - - /** - * This method returns a list of cost center objects the engine knows about - */ - virtual QList costCenterList() const = 0; - - /** - * This method is used to return the standard liability account - * @return MyMoneyAccount liability account(group) - */ - virtual MyMoneyAccount liability() const = 0; - - /** - * This method is used to return the standard asset account - * @return MyMoneyAccount asset account(group) - */ - virtual MyMoneyAccount asset() const = 0; - - /** - * This method is used to return the standard expense account - * @return MyMoneyAccount expense account(group) - */ - virtual MyMoneyAccount expense() const = 0; - - /** - * This method is used to return the standard income account - * @return MyMoneyAccount income account(group) - */ - virtual MyMoneyAccount income() const = 0; - - /** - * This method is used to return the standard equity account - * @return MyMoneyAccount equity account(group) - */ - virtual MyMoneyAccount equity() const = 0; - - /** - * This method is used to create a new account - * - * An exception will be thrown upon error conditions. - * - * @param account MyMoneyAccount filled with data - */ - virtual void addAccount(MyMoneyAccount& account) = 0; - - /** - * This method is used to add one account as sub-ordinate to another - * (parent) account. The objects that are passed will be modified - * accordingly. - * - * An exception will be thrown upon error conditions. - * - * @param parent parent account the account should be added to - * @param account the account to be added - * - * @deprecated This method is only provided as long as we provide - * the version 0.4 binary reader. As soon as we deprecate - * this compatibility mode this method will disappear from - * this interface! - */ - virtual void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) = 0; - - /** - * This method is used to create a new payee - * - * An exception will be thrown upon error conditions - * - * @param payee MyMoneyPayee reference to payee information - * - * @deprecated This method is only provided as long as we provide - * the version 0.4 binary reader. As soon as we deprecate - * this compatibility mode this method will disappear from - * this interface! - * - */ - virtual void addPayee(MyMoneyPayee& payee) = 0; - - /** - * Adds an institution to the storage. A - * respective institution-ID will be generated within this record. - * The ID is stored as QString in the object passed as argument. - * - * An exception will be thrown upon error conditions. - * - * @param institution The complete institution information in a - * MyMoneyInstitution object - * - * @deprecated This method is only provided as long as we provide - * the version 0.4 binary reader. As soon as we deprecate - * this compatibility mode this method will disappear from - * this interface! - */ - virtual void addInstitution(MyMoneyInstitution& institution) = 0; - - /** - * Adds a transaction to the file-global transaction pool. A respective - * transaction-ID will be generated within this record. The ID is stored - * as QString with the object. - * - * An exception will be thrown upon error conditions. - * - * @param transaction reference to the transaction - * @param skipAccountUpdate if set, the transaction lists of the accounts - * referenced in the splits are not updated. This is used for - * bulk loading a lot of transactions but not during normal operation. - * Refreshing the account's transaction list can be done using - * refreshAllAccountTransactionList(). - * - * @deprecated This method is only provided as long as we provide - * the version 0.4 binary reader. As soon as we deprecate - * this compatibility mode this method will disappear from - * this interface! - */ - virtual void addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate = false) = 0; - - virtual void loadAccounts(const QMap& map) = 0; - virtual void loadTransactions(const QMap& map) = 0; - virtual void loadInstitutions(const QMap& map) = 0; - virtual void loadPayees(const QMap& map) = 0; - virtual void loadTags(const QMap& map) = 0; - virtual void loadSchedules(const QMap& map) = 0; - virtual void loadSecurities(const QMap& map) = 0; - virtual void loadCurrencies(const QMap& map) = 0; - virtual void loadReports(const QMap& reports) = 0; - virtual void loadBudgets(const QMap& budgets) = 0; - virtual void loadPrices(const MyMoneyPriceList& list) = 0; - virtual void loadOnlineJobs(const QMap& onlineJobs) = 0; - virtual void loadCostCenters(const QMap& costCenters) = 0; - - virtual ulong accountId() const = 0; - virtual ulong transactionId() const = 0; - virtual ulong payeeId() const = 0; - virtual ulong tagId() const = 0; - virtual ulong institutionId() const = 0; - virtual ulong scheduleId() const = 0; - virtual ulong securityId() const = 0; - virtual ulong reportId() const = 0; - virtual ulong budgetId() const = 0; - virtual ulong onlineJobId() const = 0; - virtual ulong costCenterId() const = 0; - - virtual void loadAccountId(ulong id) = 0; - virtual void loadTransactionId(ulong id) = 0; - virtual void loadPayeeId(ulong id) = 0; - virtual void loadTagId(ulong id) = 0; - virtual void loadInstitutionId(ulong id) = 0; - virtual void loadScheduleId(ulong id) = 0; - virtual void loadSecurityId(ulong id) = 0; - virtual void loadReportId(ulong id) = 0; - virtual void loadBudgetId(ulong id) = 0; - virtual void loadOnlineJobId(ulong id) = 0; - virtual void loadCostCenterId(ulong id) = 0; - - /** - * This method is used to retrieve the whole set of key/value pairs - * from the container. It is meant to be used for permanent storage - * functionality. See MyMoneyKeyValueContainer::pairs() for details. - * - * @return QMap containing all key/value pairs of - * this container. - */ - virtual QMap pairs() const = 0; - - /** - * This method is used to initially store a set of key/value pairs - * in the container. It is meant to be used for loading functionality - * from permanent storage. See MyMoneyKeyValueContainer::setPairs() - * for details - * - * @param list const QMap containing the set of - * key/value pairs to be loaded into the container. - * - * @note All existing key/value pairs in the container will be deleted. - */ - virtual void setPairs(const QMap& list) = 0; - - virtual QList scheduleListEx(int scheduleTypes, - int scheduleOcurrences, - int schedulePaymentTypes, - QDate startDate, - const QStringList& accounts) const = 0; - - /** - * This method is used to retrieve the list of all currencies - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneySecurity objects representing a currency. - */ - virtual QList currencyList() const = 0; - - /** - * This method is used to retrieve the list of all reports - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneyReport objects. - */ - virtual QList reportList() const = 0; - - /** - * This method is used to retrieve the list of all budgets - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneyBudget objects. - */ - virtual QList budgetList() const = 0; - - - /** - * This method adds a price entry to the price list. - */ - virtual void addPrice(const MyMoneyPrice& price) = 0; - - /** - * This method returns a list of all prices. - * - * @return MyMoneyPriceList of all MyMoneyPrice objects. - */ - virtual MyMoneyPriceList priceList() const = 0; - - /** - * This method recalculates the balances of all accounts - * based on the transactions stored in the engine. - */ - virtual void rebuildAccountBalances() = 0; - -protected: - virtual QString nextAccountID() = 0; - virtual QString nextTransactionID() = 0; - virtual QString nextPayeeID() = 0; - virtual QString nextTagID() = 0; - virtual QString nextInstitutionID() = 0; - virtual QString nextScheduleID() = 0; - virtual QString nextSecurityID() = 0; - virtual QString nextReportID() = 0; - virtual QString nextBudgetID() = 0; - virtual QString nextOnlineJobID() = 0; - virtual QString nextCostCenterID() = 0; -}; - -#endif diff --git a/kmymoney/mymoney/storage/imymoneystorage.h b/kmymoney/mymoney/storage/imymoneystorage.h deleted file mode 100644 index 31877510c..000000000 --- a/kmymoney/mymoney/storage/imymoneystorage.h +++ /dev/null @@ -1,961 +0,0 @@ -/*************************************************************************** - imymoneystorage.h - description - ------------------- - begin : Sun May 5 2002 - copyright : (C) 2000-2002 by Michael Edwardes - email : mte@users.sourceforge.net - Javier Campos Morales - Felix Rodriguez - John C - Thomas Baumgart - Kevin Tambascio - (C) 2017 by Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef IMYMONEYSTORAGE_H -#define IMYMONEYSTORAGE_H - -// ---------------------------------------------------------------------------- -// QT Includes - -#include - -// ---------------------------------------------------------------------------- -// Project Includes - -class QString; -class QDate; -class QStringList; -class QBitArray; - -class MyMoneyObject; -class MyMoneyMoney; -class MyMoneyInstitution; -class MyMoneyAccount; -class MyMoneySecurity; -class MyMoneyPayee; -class MyMoneyTag; -class MyMoneyPrice; -class MyMoneyReport; -class MyMoneySchedule; -class MyMoneyBudget; -class MyMoneySplit; -class MyMoneyTransaction; -class MyMoneyTransactionFilter; -class MyMoneyCostCenter; -class onlineJob; - -template class QMap; -template class QList; -template struct QPair; - -typedef QPair MyMoneySecurityPair; -typedef QMap MyMoneyPriceEntries; -typedef QMap MyMoneyPriceList; - -namespace eMyMoney { namespace Schedule { enum class Type; } } -namespace eMyMoney { namespace Schedule { enum class Occurrence; } } -namespace eMyMoney { namespace Schedule { enum class PaymentType; } } - -/** - * @author Thomas Baumgart - * @author Łukasz Wojniłowicz - */ - -/** - * The IMyMoneyStorage class describes the interface between the MyMoneyFile class - * and the real storage manager. - * - * @see MyMoneySeqAccessMgr - */ -class IMyMoneyStorage -{ -public: - virtual ~IMyMoneyStorage() = default; - - // general get functions - virtual MyMoneyPayee user() const = 0; - virtual QDate creationDate() const = 0; - virtual QDate lastModificationDate() const = 0; - virtual uint currentFixVersion() const = 0; - virtual uint fileFixVersion() const = 0; - - // general set functions - virtual void setUser(const MyMoneyPayee& user) = 0; - virtual void setFileFixVersion(const uint v) = 0; - - // methods provided by MyMoneyKeyValueContainer - virtual void setValue(const QString& key, const QString& value) = 0; - virtual QString value(const QString& key) const = 0; - virtual void deletePair(const QString& key) = 0; - - /** - * This method is used to duplicate an IMyMoneyStorage object and return - * a pointer to the newly created copy. The caller of this method is the - * new owner of the object and must destroy it. - */ -// virtual IMyMoneyStorage const * duplicate() = 0; - - /** - * This method is used to create a new account - * - * An exception will be thrown upon error conditions. - * - * @param account MyMoneyAccount filled with data - */ - virtual void addAccount(MyMoneyAccount& account) = 0; - - /** - * This method is used to add one account as sub-ordinate to another - * (parent) account. The objects that are passed will be modified - * accordingly. - * - * An exception will be thrown upon error conditions. - * - * @param parent parent account the account should be added to - * @param account the account to be added - */ - virtual void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) = 0; - - /** - * This method is used to create a new payee - * - * An exception will be thrown upon error conditions - * - * @param payee MyMoneyPayee reference to payee information - */ - virtual void addPayee(MyMoneyPayee& payee) = 0; - - /** - * This method is used to retrieve information about a payee - * An exception will be thrown upon error conditions. - * - * @param id QString reference to id of payee - * - * @return MyMoneyPayee object of payee - */ - virtual MyMoneyPayee payee(const QString& id) const = 0; - - /** - * This method is used to retrieve the id to a corresponding - * name of a payee/receiver. - * An exception will be thrown upon error conditions. - * - * @param payee QString reference to name of payee - * - * @return MyMoneyPayee object of payee - */ - virtual MyMoneyPayee payeeByName(const QString& payee) const = 0; - - /** - * This method is used to modify an existing payee - * - * An exception will be thrown upon error conditions - * - * @param payee MyMoneyPayee reference to payee information - */ - virtual void modifyPayee(const MyMoneyPayee& payee) = 0; - - /** - * This method is used to remove an existing payee - * - * An exception will be thrown upon error conditions - * - * @param payee MyMoneyPayee reference to payee information - */ - virtual void removePayee(const MyMoneyPayee& payee) = 0; - - /** - * This method returns a list of the payees - * inside a MyMoneyStorage object - * - * @return QList containing the payee information - */ - virtual QList payeeList() const = 0; - - /** - * This method is used to create a new tag - * - * An exception will be thrown upon error conditions - * - * @param tag MyMoneyTag reference to tag information - */ - virtual void addTag(MyMoneyTag& tag) = 0; - - /** - * This method is used to retrieve information about a tag - * An exception will be thrown upon error conditions. - * - * @param id QString reference to id of tag - * - * @return MyMoneyTag object of tag - */ - virtual MyMoneyTag tag(const QString& id) const = 0; - - /** - * This method is used to retrieve the id to a corresponding - * name of a tag. - * An exception will be thrown upon error conditions. - * - * @param tag QString reference to name of tag - * - * @return MyMoneyTag object of tag - */ - virtual MyMoneyTag tagByName(const QString& tag) const = 0; - - /** - * This method is used to modify an existing tag - * - * An exception will be thrown upon error conditions - * - * @param tag MyMoneyTag reference to tag information - */ - virtual void modifyTag(const MyMoneyTag& tag) = 0; - - /** - * This method is used to remove an existing tag - * - * An exception will be thrown upon error conditions - * - * @param tag MyMoneyTag reference to tag information - */ - virtual void removeTag(const MyMoneyTag& tag) = 0; - - /** - * This method returns a list of the tags - * inside a MyMoneyStorage object - * - * @return QList containing the tag information - */ - virtual QList tagList() const = 0; - - /** - * Returns the account addressed by it's id. - * - * An exception will be thrown upon error conditions. - * - * @param id id of the account to locate. - * @return reference to MyMoneyAccount object. An exception is thrown - * if the id is unknown - */ - virtual MyMoneyAccount account(const QString& id) const = 0; - - /** - * This method is used to check whether a given - * account id references one of the standard accounts or not. - * - * An exception will be thrown upon error conditions. - * - * @param id account id - * @return true if account-id is one of the standards, false otherwise - */ - virtual bool isStandardAccount(const QString& id) const = 0; - - /** - * This method is used to set the name for the specified standard account - * within the storage area. An exception will be thrown, if an error - * occurs - * - * @param id QString reference to one of the standard accounts. - * @param name QString reference to the name to be set - * - */ - virtual void setAccountName(const QString& id, const QString& name) = 0; - - /** - * Adds an institution to the storage. A - * respective institution-ID will be generated within this record. - * The ID is stored as QString in the object passed as argument. - * - * An exception will be thrown upon error conditions. - * - * @param institution The complete institution information in a - * MyMoneyInstitution object - */ - virtual void addInstitution(MyMoneyInstitution& institution) = 0; - - /** - * Adds a transaction to the file-global transaction pool. A respective - * transaction-ID will be generated within this record. The ID is stored - * QString with the object. - * - * An exception will be thrown upon error conditions. - * - * @param transaction reference to the transaction - * @param skipAccountUpdate if set, the transaction lists of the accounts - * referenced in the splits are not updated. This is used for - * bulk loading a lot of transactions but not during normal operation - */ - virtual void addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate = false) = 0; - - /** - * This method is used to determince, if the account with the - * given ID is referenced by any split in m_transactionList. - * - * An exception will be thrown upon error conditions. - * - * @param id id of the account to be checked for - * @return true if account is referenced, false otherwise - */ - virtual bool hasActiveSplits(const QString& id) const = 0; - - /** - * This method is used to return the actual balance of an account - * without it's sub-ordinate accounts. If a @p date is presented, - * the balance at the beginning of this date (not including any - * transaction on this date) is returned. Otherwise all recorded - * transactions are included in the balance. - * - * @param id id of the account in question - * @param date return balance for specific date - * @return balance of the account as MyMoneyMoney object - */ - virtual MyMoneyMoney balance(const QString& id, const QDate& date) const = 0; - - /** - * This method is used to return the actual balance of an account - * including it's sub-ordinate accounts. If a @p date is presented, - * the balance at the beginning of this date (not including any - * transaction on this date) is returned. Otherwise all recorded - * transactions are included in the balance. - * - * @param id id of the account in question - * @param date return balance for specific date - * @return balance of the account as MyMoneyMoney object - */ - virtual MyMoneyMoney totalBalance(const QString& id, const QDate& date) const = 0; - - /** - * Returns the institution of a given ID - * - * @param id id of the institution to locate - * @return MyMoneyInstitution object filled with data. If the institution - * could not be found, an exception will be thrown - */ - virtual MyMoneyInstitution institution(const QString& id) const = 0; - - /** - * This method returns an indicator if the storage object has been - * changed after it has last been saved to permanent storage. - * - * @return true if changed, false if not - */ - virtual bool dirty() const = 0; - - /** - * This method can be used by an external object to force the - * storage object to be dirty. This is used e.g. when an upload - * to an external destination failed but the previous storage - * to a local disk was ok. - */ - virtual void setDirty() = 0; - - /** - * This method returns the number of accounts currently known to this storage - * in the range 0..MAXUINT - * - * @return number of accounts currently known inside a MyMoneyFile object - */ - virtual uint accountCount() const = 0; - - /** - * This method returns a list of the institutions - * inside a MyMoneyStorage object - * - * @return QList containing the - * institution information - */ - virtual QList institutionList() const = 0; - - /** - * Modifies an already existing account in the file global account pool. - * - * An exception will be thrown upon error conditions. - * - * @param account reference to the new account information - * @param skipCheck allows to skip the builtin consistency checks - */ - virtual void modifyAccount(const MyMoneyAccount& account, bool skipCheck = false) = 0; - - /** - * Modifies an already existing institution in the file global - * institution pool. - * - * An exception will be thrown upon error conditions. - * - * @param institution The complete new institution information - */ - virtual void modifyInstitution(const MyMoneyInstitution& institution) = 0; - - /** - * This method is used to update a specific transaction in the - * transaction pool of the MyMoneyFile object - * - * An exception will be thrown upon error conditions. - * - * @param transaction reference to transaction to be changed - */ - virtual void modifyTransaction(const MyMoneyTransaction& transaction) = 0; - - /** - * This method re-parents an existing account - * - * An exception will be thrown upon error conditions. - * - * @param account MyMoneyAccount reference to account to be re-parented - * @param parent MyMoneyAccount reference to new parent account - */ - virtual void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent) = 0; - - /** - * This method is used to remove a transaction from the transaction - * pool (journal). - * - * An exception will be thrown upon error conditions. - * - * @param transaction const reference to transaction to be deleted - */ - virtual void removeTransaction(const MyMoneyTransaction& transaction) = 0; - - /** - * This method returns the number of transactions currently known to file - * in the range 0..MAXUINT - * - * @param account QString reference to account id. If account is empty - + all transactions (the journal) will be counted. If account - * is not empty it returns the number of transactions - * that have splits in this account. - * - * @return number of transactions in journal/account - */ - virtual uint transactionCount(const QString& account) const = 0; - - /** - * This method returns a QMap filled with the number of transactions - * per account. The account id serves as index into the map. If one - * needs to have all transactionCounts() for many accounts, this method - * is faster than calling transactionCount(const QString& account) many - * times. - * - * @return QMap with numbers of transactions per account - */ - virtual QMap transactionCountMap() const = 0; - - /** - * This method is used to pull a list of transactions from the file - * global transaction pool. It returns all those transactions - * that match the filter passed as argument. If the filter is empty, - * the whole journal will be returned. - * The list returned is sorted according to the transactions posting date. - * If more than one transaction exists for the same date, the order among - * them is undefined. - * - * @param filter MyMoneyTransactionFilter object with the match criteria - * - * @return set of transactions in form of a QList - */ - virtual QList transactionList(MyMoneyTransactionFilter& filter) const = 0; - - virtual void transactionList(QList& list, MyMoneyTransactionFilter& filter) const = 0; - - virtual void transactionList(QList >& list, MyMoneyTransactionFilter& filter) const = 0; - - /** - * Deletes an existing account from the file global account pool - * This method only allows to remove accounts that are not - * referenced by any split. Use moveSplits() to move splits - * to another account. An exception is thrown in case of a - * problem. - * - * @param account reference to the account to be deleted. - */ - virtual void removeAccount(const MyMoneyAccount& account) = 0; - - /** - * Deletes an existing institution from the file global institution pool - * Also modifies the accounts that reference this institution as - * their institution. - * - * An exception will be thrown upon error conditions. - * - * @param institution institution to be deleted. - */ - virtual void removeInstitution(const MyMoneyInstitution& institution) = 0; - - /** - * This method is used to extract a transaction from the file global - * transaction pool through an id. In case of an invalid id, an - * exception will be thrown. - * - * @param id id of transaction as QString. - * @return reference to the requested transaction - */ - virtual MyMoneyTransaction transaction(const QString& id) const = 0; - - /** - * This method is used to extract a transaction from the file global - * transaction pool through an index into an account. - * - * @param account id of the account as QString - * @param idx number of transaction in this account - * @return reference to MyMoneyTransaction object - */ - virtual MyMoneyTransaction transaction(const QString& account, const int idx) const = 0; - - /** - * This method returns the number of institutions currently known to file - * in the range 0..MAXUINT - * - * @return number of institutions known to file - */ - virtual uint institutionCount() const = 0; - - /** - * This method returns a list of accounts inside the storage object. - * - * @param list reference to QList receiving the account objects - * - * @note The standard accounts will not be returned - */ - virtual void accountList(QList& list) const = 0; - - /** - * This method is used to return the standard liability account - * @return MyMoneyAccount liability account(group) - */ - virtual MyMoneyAccount liability() const = 0; - - /** - * This method is used to return the standard asset account - * @return MyMoneyAccount asset account(group) - */ - virtual MyMoneyAccount asset() const = 0; - - /** - * This method is used to return the standard expense account - * @return MyMoneyAccount expense account(group) - */ - virtual MyMoneyAccount expense() const = 0; - - /** - * This method is used to return the standard income account - * @return MyMoneyAccount income account(group) - */ - virtual MyMoneyAccount income() const = 0; - - /** - * This method is used to return the standard equity account - * @return MyMoneyAccount equity account(group) - */ - virtual MyMoneyAccount equity() const = 0; - - /** - * This method is used to create a new security object. The ID will be created - * automatically. The object passed with the parameter @p security is modified - * to contain the assigned id. - * - * An exception will be thrown upon error conditions. - * - * @param security MyMoneySecurity filled with data - */ - virtual void addSecurity(MyMoneySecurity& security) = 0; - - /** - * This method is used to modify an existing MyMoneySecurity - * object. - * - * An exception will be thrown upon erroneous situations. - * - * @param security reference to the MyMoneySecurity object to be updated - */ - virtual void modifySecurity(const MyMoneySecurity& security) = 0; - - /** - * This method is used to remove an existing MyMoneySecurity object - * from the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @param security reference to the MyMoneySecurity object to be removed - */ - virtual void removeSecurity(const MyMoneySecurity& security) = 0; - - /** - * This method is used to retrieve a single MyMoneySecurity object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneySecurity object - * @return MyMoneySecurity object - */ - virtual MyMoneySecurity security(const QString& id) const = 0; - - /** - * This method returns a list of the security objects - * inside a MyMoneyStorage object - * - * @return QList containing objects - */ - virtual QList securityList() const = 0; - - virtual void addPrice(const MyMoneyPrice& price) = 0; - virtual void removePrice(const MyMoneyPrice& price) = 0; - virtual MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& date, bool exactDate) const = 0; - - /** - * This method returns a list of all prices. - * - * @return MyMoneyPriceList of all MyMoneyPrice objects. - */ - virtual MyMoneyPriceList priceList() const = 0; - - /** - * This method is used to add a scheduled transaction to the engine. - * It must be sure, that the id of the object is not filled. When the - * method returns to the caller, the id will be filled with the - * newly created object id value. - * - * An exception will be thrown upon erroneous situations. - * - * @param sched reference to the MyMoneySchedule object - */ - virtual void addSchedule(MyMoneySchedule& sched) = 0; - - /** - * This method is used to modify an existing MyMoneySchedule - * object. Therefor, the id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param sched const reference to the MyMoneySchedule object to be updated - */ - virtual void modifySchedule(const MyMoneySchedule& sched) = 0; - - /** - * This method is used to remove an existing MyMoneySchedule object - * from the engine. The id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param sched const reference to the MyMoneySchedule object to be updated - */ - virtual void removeSchedule(const MyMoneySchedule& sched) = 0; - - /** - * This method is used to retrieve a single MyMoneySchedule object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneySchedule object - * @return MyMoneySchedule object - */ - virtual MyMoneySchedule schedule(const QString& id) const = 0; - - /** - * This method is used to extract a list of scheduled transactions - * according to the filter criteria passed as arguments. - * - * @param accountId only search for scheduled transactions that reference - * accound @p accountId. If accountId is the empty string, - * this filter is off. Default is @p QString(). - * @param type only schedules of type @p type are searched for. - * See eMyMoney::Schedule::Type for details. - * Default is eMyMoney::Schedule::Type::Any - * @param occurrence only schedules of occurrence type @p occurrence are searched for. - * See eMyMoney::Schedule::Occurrence for details. - * Default is eMyMoney::Schedule::Occurrence::Any - * @param paymentType only schedules of payment method @p paymentType - * are searched for. - * See eMyMoney::Schedule::PaymentType for details. - * Default is eMyMoney::Schedule::PaymentType::Any - * @param startDate only schedules with payment dates after @p startDate - * are searched for. Default is all dates (QDate()). - * @param endDate only schedules with payment dates ending prior to @p endDate - * are searched for. Default is all dates (QDate()). - * @param overdue if true, only those schedules that are overdue are - * searched for. Default is false (all schedules will be returned). - * - * @return QList list of schedule objects. - */ - virtual QList scheduleList(const QString& accountId, - eMyMoney::Schedule::Type type, - eMyMoney::Schedule::Occurrence occurrence, - eMyMoney::Schedule::PaymentType paymentType, - const QDate& startDate, - const QDate& endDate, - bool overdue) const = 0; - - virtual QList scheduleListEx(int scheduleTypes, - int scheduleOcurrences, - int schedulePaymentTypes, - QDate startDate, - const QStringList& accounts) const = 0; - - /** - * This method is used to add a new currency object to the engine. - * The ID of the object is the trading symbol, so there is no need for an additional - * ID since the symbol is guaranteed to be unique. - * - * An exception will be thrown upon erroneous situations. - * - * @param currency reference to the MyMoneySecurity object - */ - virtual void addCurrency(const MyMoneySecurity& currency) = 0; - - /** - * This method is used to modify an existing MyMoneySecurity - * object. - * - * An exception will be thrown upon erroneous situations. - * - * @param currency reference to the MyMoneyCurrency object - */ - virtual void modifyCurrency(const MyMoneySecurity& currency) = 0; - - /** - * This method is used to remove an existing MyMoneySecurity object - * from the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @param currency reference to the MyMoneySecurity object - */ - virtual void removeCurrency(const MyMoneySecurity& currency) = 0; - - /** - * This method is used to retrieve a single MyMoneySecurity object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneySecurity object - * @return MyMoneyCurrency object - */ - virtual MyMoneySecurity currency(const QString& id) const = 0; - - /** - * This method is used to retrieve the list of all currencies - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneySecurity objects representing a currency. - */ - virtual QList currencyList() const = 0; - - /** - * This method is used to retrieve the list of all reports - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneyReport objects. - */ - virtual QList reportList() const = 0; - - /** - * This method is used to add a new report to the engine. - * It must be sure, that the id of the object is not filled. When the - * method returns to the caller, the id will be filled with the - * newly created object id value. - * - * An exception will be thrown upon erroneous situations. - * - * @param report reference to the MyMoneyReport object - */ - virtual void addReport(MyMoneyReport& report) = 0; - - /** - * This method is used to modify an existing MyMoneyReport - * object. Therefor, the id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param report const reference to the MyMoneyReport object to be updated - */ - virtual void modifyReport(const MyMoneyReport& report) = 0; - - /** - * This method returns the number of reports currently known to file - * in the range 0..MAXUINT - * - * @return number of reports known to file - */ - virtual uint countReports() const = 0; - - /** - * This method is used to retrieve a single MyMoneyReport object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneyReport object - * @return MyMoneyReport object - */ - virtual MyMoneyReport report(const QString& id) const = 0; - - /** - * This method is used to remove an existing MyMoneyReport object - * from the engine. The id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param report const reference to the MyMoneyReport object to be updated - */ - virtual void removeReport(const MyMoneyReport& report) = 0; - - /** - * This method is used to retrieve the list of all budgets - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneyBudget objects. - */ - virtual QList budgetList() const = 0; - - /** - * This method is used to add a new budget to the engine. - * It must be sure, that the id of the object is not filled. When the - * method returns to the caller, the id will be filled with the - * newly created object id value. - * - * An exception will be thrown upon erroneous situations. - * - * @param budget reference to the MyMoneyBudget object - */ - virtual void addBudget(MyMoneyBudget& budget) = 0; - - /** - * This method is used to retrieve the id to a corresponding - * name of a budget - * An exception will be thrown upon error conditions. - * - * @param budget QString reference to name of budget - * - * @return MyMoneyBudget object of budget - */ - virtual MyMoneyBudget budgetByName(const QString& budget) const = 0; - - /** - * This method is used to modify an existing MyMoneyBudget - * object. Therefor, the id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param budget const reference to the MyMoneyBudget object to be updated - */ - virtual void modifyBudget(const MyMoneyBudget& budget) = 0; - - /** - * This method returns the number of budgets currently known to file - * in the range 0..MAXUINT - * - * @return number of budgets known to file - */ - virtual uint countBudgets() const = 0; - - /** - * This method is used to retrieve a single MyMoneyBudget object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneyBudget object - * @return MyMoneyBudget object - */ - virtual MyMoneyBudget budget(const QString& id) const = 0; - - /** - * This method is used to remove an existing MyMoneyBudget object - * from the engine. The id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param budget const reference to the MyMoneyBudget object to be updated - */ - virtual void removeBudget(const MyMoneyBudget& budget) = 0; - - - /** - * This method is used to add a new onlineJob. The id will be - * overwritten. - * - * An exception will be thrown upon erronous situations. - * - * @param job The onlineJob, caller remains owner of it. Id might be updated. - */ - virtual void addOnlineJob(onlineJob& job) = 0; - - /** - * @brief Saves a onlineJob - * @param job - */ - virtual void modifyOnlineJob(const onlineJob& job) = 0; - - /** - * @brief Get onlineJob by id - * - * @return onlineJob you are not the owner nor allowed to delete it. - * @throw MyMoneyException if jobId was not found - */ - virtual onlineJob getOnlineJob(const QString& jobId) const = 0; - - /** - * @brief Return all onlineJobs - */ - virtual QList onlineJobList() const = 0; - - /** - * @brief Remove an onlineJobs - */ - virtual void removeOnlineJob(const onlineJob&) = 0; - - /** - * @brief Returns a cost center by id - */ - virtual MyMoneyCostCenter costCenter(const QString& id) const = 0; - - /** - * @brief Retruns a list of all costcenters - */ - virtual QList costCenterList() const = 0; - - /** - * This method checks, if the given @p object is referenced - * by another engine object. - * - * @param obj const reference to object to be checked - * @param skipCheck QBitArray with eStorage::Reference bits set for which - * the check should be skipped - * - * @retval false @p object is not referenced - * @retval true @p institution is referenced - */ - virtual bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const = 0; - - /** - * This method is provided to allow closing of the database before logoff - */ - virtual void close() = 0; - - /** - * These methods have to be provided to allow transaction safe data handling. - */ - virtual void startTransaction() = 0; - virtual bool commitTransaction() = 0; - virtual void rollbackTransaction() = 0; -}; - -#endif diff --git a/kmymoney/mymoney/storage/imymoneystorageformat.cpp b/kmymoney/mymoney/storage/imymoneystorageformat.cpp index e0ac13f8b..ca87efe1b 100644 --- a/kmymoney/mymoney/storage/imymoneystorageformat.cpp +++ b/kmymoney/mymoney/storage/imymoneystorageformat.cpp @@ -1,31 +1,31 @@ /*************************************************************************** imymoneystorageformat.cpp - description ------------------- begin : Sun Oct 27 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "imymoneystorageformat.h" -IMyMoneyStorageFormat::IMyMoneyStorageFormat() +IMyMoneyOperationsFormat::IMyMoneyOperationsFormat() { } -IMyMoneyStorageFormat::~IMyMoneyStorageFormat() +IMyMoneyOperationsFormat::~IMyMoneyOperationsFormat() { } diff --git a/kmymoney/mymoney/storage/imymoneystorageformat.h b/kmymoney/mymoney/storage/imymoneystorageformat.h index 5638ccb44..78cc800c6 100644 --- a/kmymoney/mymoney/storage/imymoneystorageformat.h +++ b/kmymoney/mymoney/storage/imymoneystorageformat.h @@ -1,74 +1,73 @@ /*************************************************************************** imymoneystorageformat.h - description ------------------- begin : Sun Oct 27 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef IMYMONEYSTORAGEFORMAT_H #define IMYMONEYSTORAGEFORMAT_H /** * @author Kevin Tambascio (ktambascio@yahoo.com) */ // ---------------------------------------------------------------------------- // QT Includes class QString; class QIODevice; // ---------------------------------------------------------------------------- // Project Includes -class IMyMoneySerialize; +class MyMoneyStorageMgr; - -class IMyMoneyStorageFormat +class IMyMoneyOperationsFormat { public: - IMyMoneyStorageFormat(); - virtual ~IMyMoneyStorageFormat(); + IMyMoneyOperationsFormat(); + virtual ~IMyMoneyOperationsFormat(); enum fileVersionDirectionType { Reading = 0, /**< version of file to be read */ Writing = 1 /**< version to be used when writing a file */ }; - virtual void readFile(QIODevice* qf, IMyMoneySerialize* storage) = 0; - // virtual void readStream(QDataStream& s, IMyMoneySerialize* storage) = 0; + virtual void readFile(QIODevice* qf, MyMoneyStorageMgr* storage) = 0; + // virtual void readStream(QDataStream& s, IMyMoneySerialization* storage) = 0; - virtual void writeFile(QIODevice* qf, IMyMoneySerialize* storage) = 0; - //virtual void writeStream(QDataStream& s, IMyMoneySerialize* storage) = 0; + virtual void writeFile(QIODevice* qf, MyMoneyStorageMgr* storage) = 0; + //virtual void writeStream(QDataStream& s, IMyMoneySerialization* storage) = 0; virtual void setProgressCallback(void(*callback)(int, int, const QString&)) = 0; /** * This member is used to store the file version information * obtained while reading a file. */ static unsigned int fileVersionRead; /** * This member is used to store the file version information * to be used when writing a file. */ static unsigned int fileVersionWrite; }; #endif diff --git a/kmymoney/mymoney/storage/mymoneydatabasemgr.cpp b/kmymoney/mymoney/storage/mymoneydatabasemgr.cpp deleted file mode 100644 index 96ab71517..000000000 --- a/kmymoney/mymoney/storage/mymoneydatabasemgr.cpp +++ /dev/null @@ -1,2440 +0,0 @@ -/*************************************************************************** - mymoneydatabasemgr.cpp - ------------------- - begin : June 5 2007 - copyright : (C) 2007 by Fernando Vilas - email : Fernando Vilas - 2017 by Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "mymoneydatabasemgr_p.h" - -// ---------------------------------------------------------------------------- -// QT Includes - -// ---------------------------------------------------------------------------- -// KDE Includes - -// ---------------------------------------------------------------------------- -// Project Includes - -#include "mymoneytransaction.h" -#include "mymoneyprice.h" -#include "onlinejob.h" -#include "mymoneystoragenames.h" - -using namespace eStorage; -using namespace MyMoneyStandardAccounts; - -const int INSTITUTION_ID_SIZE = 6; -const int ACCOUNT_ID_SIZE = 6; -const int TRANSACTION_ID_SIZE = 18; -const int PAYEE_ID_SIZE = 6; -const int TAG_ID_SIZE = 6; -const int SCHEDULE_ID_SIZE = 6; -const int SECURITY_ID_SIZE = 6; -const int REPORT_ID_SIZE = 6; -const int BUDGET_ID_SIZE = 6; -const int ONLINEJOB_ID_SIZE = 8; -const int PAYEEIDENTIFIER_ID_SIZE = 6; -const int COSTCENTER_ID_SIZE = 6; - -// Increment this to force an update in KMMView. -// This is different from the db schema version stored in -// MMStorageSql::m_majorVersion -const int CURRENT_FIX_VERSION = 4; - -MyMoneyDatabaseMgr::MyMoneyDatabaseMgr() : - d_ptr(new MyMoneyDatabaseMgrPrivate(this)) -{ -} - -MyMoneyDatabaseMgr::~MyMoneyDatabaseMgr() -{ - Q_D(MyMoneyDatabaseMgr); - delete d; -} - -// general get functions -MyMoneyPayee MyMoneyDatabaseMgr::user() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_user; -} - -QDate MyMoneyDatabaseMgr::creationDate() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_creationDate; -} - -QDate MyMoneyDatabaseMgr::lastModificationDate() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_lastModificationDate; -} - -uint MyMoneyDatabaseMgr::currentFixVersion() const -{ - return CURRENT_FIX_VERSION; -} - -uint MyMoneyDatabaseMgr::fileFixVersion() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_fileFixVersion; -} - -// general set functions -void MyMoneyDatabaseMgr::setUser(const MyMoneyPayee& user) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_user = user; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - d->m_sql->modifyUserInfo(user); - } -} - -void MyMoneyDatabaseMgr::setFileFixVersion(uint v) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_fileFixVersion = v; -} - -// methods provided by MyMoneyKeyValueContainer -QString MyMoneyDatabaseMgr::value(const QString& key) const -{ - return MyMoneyKeyValueContainer::value(key); -} - -void MyMoneyDatabaseMgr::setValue(const QString& key, const QString& val) -{ - MyMoneyKeyValueContainer::setValue(key, val); -} - -void MyMoneyDatabaseMgr::deletePair(const QString& key) -{ - MyMoneyKeyValueContainer::deletePair(key); -} - -QMap MyMoneyDatabaseMgr::pairs() const -{ - return MyMoneyKeyValueContainer::pairs(); -} - -void MyMoneyDatabaseMgr::setPairs(const QMap& list) -{ - MyMoneyKeyValueContainer::setPairs(list); -} - -//MyMoneyDatabaseMgr const * MyMoneyDatabaseMgr::duplicate() -//{ -// MyMoneyDatabaseMgr* that = new MyMoneyDatabaseMgr(); -// *that = *this; -// return that; -//} - -void MyMoneyDatabaseMgr::addAccount(MyMoneyAccount& account) -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - - // create the account. - MyMoneyAccount newAccount(nextAccountID(), account); - - d->m_sql->addAccount(newAccount); - account = newAccount; - } -} - -void MyMoneyDatabaseMgr::addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) -{ - Q_D(MyMoneyDatabaseMgr); - QMap accountList; - QStringList accountIdList; - QMap::ConstIterator theParent; - QMap::ConstIterator theChild; - - accountIdList << parent.id() << account.id(); - startTransaction(); - accountList = d->m_sql->fetchAccounts(accountIdList, true); - - theParent = accountList.constFind(parent.id()); - if (theParent == accountList.constEnd()) { - QString msg = "Unknown parent account '"; - msg += parent.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - theChild = accountList.constFind(account.id()); - if (theChild == accountList.constEnd()) { - QString msg = "Unknown child account '"; - msg += account.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - MyMoneyAccount acc = *theParent; - acc.addAccountId(account.id()); - parent = acc; - - acc = *theChild; - acc.setParentAccountId(parent.id()); - account = acc; - -//FIXME: MyMoneyBalanceCacheItem balance; -//FIXME: m_balanceCache[account.id()] = balance; - - QList aList; - aList << parent << account; - d->m_sql->modifyAccountList(aList); - commitTransaction(); -} - -void MyMoneyDatabaseMgr::addPayee(MyMoneyPayee& payee) -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen()) - static_cast(d->m_sql.data())->open(); - // create the payee - MyMoneyPayee newPayee(nextPayeeID(), payee); - - d->m_sql->addPayee(newPayee); - payee = newPayee; - } -} - -MyMoneyPayee MyMoneyDatabaseMgr::payee(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap::ConstIterator it; - QMap payeeList = d->m_sql->fetchPayees(QStringList(id)); - it = payeeList.constFind(id); - if (it == payeeList.constEnd()) - throw MYMONEYEXCEPTION("Unknown payee '" + id + '\''); - - return *it; -} - -MyMoneyPayee MyMoneyDatabaseMgr::payeeByName(const QString& payee) const -{ - Q_D(const MyMoneyDatabaseMgr); - if (payee.isEmpty()) - return MyMoneyPayee::null; - - QMap payeeList; - - try { - payeeList = d->m_sql->fetchPayees(); - } catch (const MyMoneyException &) { - throw; - } - - QMap::ConstIterator it_p; - - for (it_p = payeeList.constBegin(); it_p != payeeList.constEnd(); ++it_p) { - if ((*it_p).name() == payee) { - return *it_p; - } - } - - throw MYMONEYEXCEPTION("Unknown payee '" + payee + '\''); -} - -void MyMoneyDatabaseMgr::modifyPayee(const MyMoneyPayee& payee) -{ - Q_D(MyMoneyDatabaseMgr); - QMap payeeList = d->m_sql->fetchPayees(QStringList(payee.id()), true); - QMap::ConstIterator it; - - it = payeeList.constFind(payee.id()); - if (it == payeeList.constEnd()) { - QString msg = "Unknown payee '" + payee.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - d->m_sql->modifyPayee(payee); -} - -void MyMoneyDatabaseMgr::removePayee(const MyMoneyPayee& payee) -{ - Q_D(MyMoneyDatabaseMgr); - QMap::ConstIterator it_t; - QMap::ConstIterator it_s; - QMap payeeList = d->m_sql->fetchPayees(QStringList(payee.id())); - QMap::ConstIterator it_p; - - it_p = payeeList.constFind(payee.id()); - if (it_p == payeeList.constEnd()) { - QString msg = "Unknown payee '" + payee.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - // scan all transactions to check if the payee is still referenced - - QMap transactionList = d->m_sql->fetchTransactions(); // make sure they're all here - for (it_t = transactionList.constBegin(); it_t != transactionList.constEnd(); ++it_t) { - if ((*it_t).hasReferenceTo(payee.id())) { - throw MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("transaction")); - } - } - - // check referential integrity in schedules - QMap scheduleList = d->m_sql->fetchSchedules(); // make sure they're all here - for (it_s = scheduleList.constBegin(); it_s != scheduleList.constEnd(); ++it_s) { - if ((*it_s).hasReferenceTo(payee.id())) { - throw MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("schedule")); - } - } - // remove any reference to report and/or budget - d->removeReferences(payee.id()); - - d->m_sql->removePayee(payee); -} - -QList MyMoneyDatabaseMgr::payeeList() const -{ - Q_D(const MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->fetchPayees().values(); - } else - return QList (); -} - -void MyMoneyDatabaseMgr::addTag(MyMoneyTag& tag) -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - // create the tag - MyMoneyTag newTag(nextTagID(), tag); - - d->m_sql->addTag(newTag); - tag = newTag; - } -} - -MyMoneyTag MyMoneyDatabaseMgr::tag(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap::ConstIterator it; - QMap tagList = d->m_sql->fetchTags(QStringList(id)); - it = tagList.constFind(id); - if (it == tagList.constEnd()) - throw MYMONEYEXCEPTION("Unknown tag '" + id + '\''); - - return *it; -} - -MyMoneyTag MyMoneyDatabaseMgr::tagByName(const QString& tag) const -{ - Q_D(const MyMoneyDatabaseMgr); - if (tag.isEmpty()) - return MyMoneyTag::null; - - QMap tagList; - - try { - tagList = d->m_sql->fetchTags(); - } catch (const MyMoneyException &) { - throw; - } - - QMap::ConstIterator it_ta; - - for (it_ta = tagList.constBegin(); it_ta != tagList.constEnd(); ++it_ta) { - if ((*it_ta).name() == tag) { - return *it_ta; - } - } - - throw MYMONEYEXCEPTION("Unknown tag '" + tag + '\''); -} - -void MyMoneyDatabaseMgr::modifyTag(const MyMoneyTag& tag) -{ - Q_D(MyMoneyDatabaseMgr); - QMap tagList = d->m_sql->fetchTags(QStringList(tag.id()), true); - QMap::ConstIterator it; - - it = tagList.constFind(tag.id()); - if (it == tagList.constEnd()) { - QString msg = "Unknown tag '" + tag.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - d->m_sql->modifyTag(tag); -} - -void MyMoneyDatabaseMgr::removeTag(const MyMoneyTag& tag) -{ - Q_D(MyMoneyDatabaseMgr); - QMap::ConstIterator it_t; - QMap::ConstIterator it_s; - QMap tagList = d->m_sql->fetchTags(QStringList(tag.id())); - QMap::ConstIterator it_ta; - - it_ta = tagList.constFind(tag.id()); - if (it_ta == tagList.constEnd()) { - QString msg = "Unknown tag '" + tag.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - // scan all transactions to check if the tag is still referenced - - QMap transactionList = d->m_sql->fetchTransactions(); // make sure they're all here - for (it_t = transactionList.constBegin(); it_t != transactionList.constEnd(); ++it_t) { - if ((*it_t).hasReferenceTo(tag.id())) { - throw MYMONEYEXCEPTION(QString("Cannot remove tag that is still referenced to a %1").arg("transaction")); - } - } - - // check referential integrity in schedules - QMap scheduleList = d->m_sql->fetchSchedules(); // make sure they're all here - for (it_s = scheduleList.constBegin(); it_s != scheduleList.constEnd(); ++it_s) { - if ((*it_s).hasReferenceTo(tag.id())) { - throw MYMONEYEXCEPTION(QString("Cannot remove tag that is still referenced to a %1").arg("schedule")); - } - } - // remove any reference to report and/or budget - d->removeReferences(tag.id()); - - d->m_sql->removeTag(tag); -} - -QList MyMoneyDatabaseMgr::tagList() const -{ - Q_D(const MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->fetchTags().values(); - } else { - return QList (); - } -} - -void MyMoneyDatabaseMgr::modifyOnlineJob(const onlineJob& job) -{ - Q_D(MyMoneyDatabaseMgr); - if (job.id().isEmpty()) - throw MYMONEYEXCEPTION("empty online job id"); - d->m_sql->modifyOnlineJob(job); -} - -void MyMoneyDatabaseMgr::addOnlineJob(onlineJob& job) -{ - Q_D(MyMoneyDatabaseMgr); - job = onlineJob(nextOnlineJobID(), job); - d->m_sql->addOnlineJob(job); -} - -onlineJob MyMoneyDatabaseMgr::getOnlineJob(const QString &jobId) const -{ - Q_D(const MyMoneyDatabaseMgr); - if (jobId.isEmpty()) - throw MYMONEYEXCEPTION("empty online job id"); - - if (d->m_sql) { - if (! d->m_sql->isOpen()) - ((QSqlDatabase*)(d->m_sql.data()))->open(); - - QMap jobList = d->m_sql->fetchOnlineJobs(QStringList(jobId)); - QMap ::ConstIterator pos = jobList.constFind(jobId); - - // locate the account and if present, return it's data - if (pos != jobList.constEnd()) - return *pos; - } else { - throw MYMONEYEXCEPTION("No database connected"); - } - - // throw an exception, if it does not exist - throw MYMONEYEXCEPTION(QLatin1String("Unknown online job id '") + jobId + QLatin1Char('\'')); -} - -QList MyMoneyDatabaseMgr::onlineJobList() const -{ - Q_D(const MyMoneyDatabaseMgr); - if (d->m_sql) { - if (!d->m_sql->isOpen()) - ((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->fetchOnlineJobs().values(); - } - return QList(); -} - -void MyMoneyDatabaseMgr::removeOnlineJob(const onlineJob& job) -{ - Q_D(MyMoneyDatabaseMgr); - if (job.id().isEmpty()) - throw MYMONEYEXCEPTION("Empty online job id during remove."); - - d->m_sql->removeOnlineJob(job); -} - -MyMoneyAccount MyMoneyDatabaseMgr::account(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - if (id.isEmpty()) { - throw MYMONEYEXCEPTION("empty account id"); - } - - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - QMap accountList = d->m_sql->fetchAccounts(QStringList(id)); - QMap ::ConstIterator pos = accountList.constFind(id); - - // locate the account and if present, return it's data - if (pos != accountList.constEnd()) - return *pos; - } else { - throw MYMONEYEXCEPTION("No database connected"); - } - - // throw an exception, if it does not exist - QString msg = "Unknown account id '" + id + '\''; - throw MYMONEYEXCEPTION(msg); -} - -bool MyMoneyDatabaseMgr::isStandardAccount(const QString& id) const -{ - return id == stdAccNames[stdAccLiability] - || id == stdAccNames[stdAccAsset] - || id == stdAccNames[stdAccExpense] - || id == stdAccNames[stdAccIncome] - || id == stdAccNames[stdAccEquity]; -} - -void MyMoneyDatabaseMgr::setAccountName(const QString& id, const QString& name) -{ - Q_D(MyMoneyDatabaseMgr); - if (!isStandardAccount(id)) - throw MYMONEYEXCEPTION("Only standard accounts can be modified using setAccountName()"); - - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - startTransaction(); - MyMoneyAccount acc = d->m_sql->fetchAccounts(QStringList(id), true)[id]; - acc.setName(name); - d->m_sql->modifyAccount(acc); - commitTransaction(); - } -} - -void MyMoneyDatabaseMgr::addInstitution(MyMoneyInstitution& institution) -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - MyMoneyInstitution newInstitution(nextInstitutionID(), institution); - - // mark file as changed - d->m_sql->addInstitution(newInstitution); - - // return new data - institution = newInstitution; - } -} - -QString MyMoneyDatabaseMgr::nextPayeeID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementPayeeId())); - id = 'P' + id.rightJustified(PAYEE_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextTagID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementTagId())); - id = 'G' + id.rightJustified(TAG_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextInstitutionID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementInstitutionId())); - id = 'I' + id.rightJustified(INSTITUTION_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextAccountID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementAccountId())); - id = 'A' + id.rightJustified(ACCOUNT_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextBudgetID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementBudgetId())); - id = 'B' + id.rightJustified(BUDGET_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextReportID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementReportId())); - id = 'R' + id.rightJustified(REPORT_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextTransactionID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementTransactionId())); - id = 'T' + id.rightJustified(TRANSACTION_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextScheduleID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementScheduleId())); - id = "SCH" + id.rightJustified(SCHEDULE_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextSecurityID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(ulong(d->m_sql->incrementSecurityId())); - id = 'E' + id.rightJustified(SECURITY_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextOnlineJobID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(d->m_sql->incrementOnlineJobId()); - id = QLatin1Char('O') + id.rightJustified(ONLINEJOB_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextPayeeIdentifierID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(d->m_sql->incrementPayeeIdentfierId()); - id = QLatin1String("IDENT") + id.rightJustified(PAYEEIDENTIFIER_ID_SIZE, '0'); - } - return id; -} - -QString MyMoneyDatabaseMgr::nextCostCenterID() -{ - Q_D(MyMoneyDatabaseMgr); - QString id; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - id.setNum(d->m_sql->incrementCostCenterId()); - id = QLatin1Char('C') + id.rightJustified(COSTCENTER_ID_SIZE, '0'); - } - return id; -} - -void MyMoneyDatabaseMgr::addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate) -{ - Q_D(MyMoneyDatabaseMgr); - // perform some checks to see that the transaction stuff is OK. For - // now we assume that - // * no ids are assigned - // * the date valid (must not be empty) - // * the referenced accounts in the splits exist - - // first perform all the checks - if (!transaction.id().isEmpty()) - throw MYMONEYEXCEPTION("transaction already contains an id"); - if (!transaction.postDate().isValid()) - throw MYMONEYEXCEPTION("invalid post date"); - - // now check the splits - foreach (const MyMoneySplit& it_s, transaction.splits()) { - // the following lines will throw an exception if the - // account or payee do not exist - account(it_s.accountId()); - if (!it_s.payeeId().isEmpty()) - payee(it_s.payeeId()); - } - - MyMoneyTransaction newTransaction(nextTransactionID(), transaction); - QString key = newTransaction.uniqueSortKey(); - - d->m_sql->addTransaction(newTransaction); - - transaction = newTransaction; - - QList aList; - // adjust the balance of all affected accounts - foreach (const MyMoneySplit& it_s, transaction.splits()) { - MyMoneyAccount acc = account(it_s.accountId()); - acc.adjustBalance(it_s); - if (!skipAccountUpdate) { - acc.touch(); -//FIXME: invalidateBalanceCache(acc.id()); - } - aList << acc; - } - d->m_sql->modifyAccountList(aList); -} - -bool MyMoneyDatabaseMgr::hasActiveSplits(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap::ConstIterator it; - - MyMoneyTransactionFilter f(id); - QMap transactionList = d->m_sql->fetchTransactions(f); - - for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) { - if ((*it).accountReferenced(id)) { - return true; - } - } - return false; -} - -/** - * This method is used to return the actual balance of an account - * without it's sub-ordinate accounts. If a @p date is presented, - * the balance at the beginning of this date (not including any - * transaction on this date) is returned. Otherwise all recorded - * transactions are included in the balance. - * - * @param id id of the account in question - * @param date return balance for specific date - * @return balance of the account as MyMoneyMoney object - */ -//MyMoneyMoney MyMoneyDatabaseMgr::balance(const QString& id, const QDate& date); - -MyMoneyMoney MyMoneyDatabaseMgr::totalBalance(const QString& id, const QDate& date) const -{ - Q_D(const MyMoneyDatabaseMgr); - QStringList accounts; - - MyMoneyMoney result; //(balance(id, date)); - - accounts = MyMoneyFile::instance()->account(id).accountList(); - foreach (const QString& it_a, accounts) { - if (!it_a.isEmpty()) { - accounts << MyMoneyFile::instance()->account(it_a).accountList(); - } - } - - // convert into a sorted list with each account only once - QMap tempList; - foreach (const QString& it_a, accounts) { - tempList[it_a] = true; - } - accounts = tempList.uniqueKeys(); - - QMap balanceMap = d->m_sql->fetchBalance(accounts, date); - for (QMap::ConstIterator it_b = balanceMap.constBegin(); it_b != balanceMap.constEnd(); ++it_b) { - result += it_b.value(); - } - - return result; -} - -MyMoneyInstitution MyMoneyDatabaseMgr::institution(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap::ConstIterator pos; - QMap institutionList = d->m_sql->fetchInstitutions(QStringList(id)); - - pos = institutionList.constFind(id); - if (pos != institutionList.constEnd()) - return *pos; - throw MYMONEYEXCEPTION("unknown institution"); -} - -bool MyMoneyDatabaseMgr::dirty() const -{ - return false; -} - -void MyMoneyDatabaseMgr::setDirty() -{} - -uint MyMoneyDatabaseMgr::accountCount() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getRecCount("kmmAccounts"); -} - -QList MyMoneyDatabaseMgr::institutionList() const -{ - Q_D(const MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->fetchInstitutions().values(); - } else { - return QList (); - } -} - -void MyMoneyDatabaseMgr::modifyAccount(const MyMoneyAccount& account, bool skipCheck) -{ - Q_D(MyMoneyDatabaseMgr); - QMap::ConstIterator pos; - - // locate the account in the file global pool - startTransaction(); - QMap accountList = d->m_sql->fetchAccounts(QStringList(account.id()), true); - pos = accountList.constFind(account.id()); - if (pos != accountList.constEnd()) { - // check if the new info is based on the old one. - // this is the case, when the file and the id - // as well as the type are equal. - if (((*pos).parentAccountId() == account.parentAccountId() - && ((*pos).accountType() == account.accountType() - || ((*pos).isLiquidAsset() && account.isLiquidAsset()))) - || skipCheck == true) { - // make sure that all the referenced objects exist - if (!account.institutionId().isEmpty()) - institution(account.institutionId()); - - //FIXME: fetch the whole list at once - foreach (const QString& it_a, account.accountList()) { - this->account(it_a); - } - - // update information in account list - //m_accountList.modify(account.id(), account); - - // invalidate cached balance -//FIXME: invalidateBalanceCache(account.id()); - - // mark file as changed - d->m_sql->modifyAccount(account); - commitTransaction(); - } else { - rollbackTransaction(); - throw MYMONEYEXCEPTION("Invalid information for update"); - } - - } else { - rollbackTransaction(); - throw MYMONEYEXCEPTION("Unknown account id"); - } -} - -void MyMoneyDatabaseMgr::modifyInstitution(const MyMoneyInstitution& institution) -{ - Q_D(MyMoneyDatabaseMgr); - QMap institutionList = d->m_sql->fetchInstitutions(QStringList(institution.id())); - QMap::ConstIterator pos; - - // locate the institution in the file global pool - pos = institutionList.constFind(institution.id()); - if (pos != institutionList.constEnd()) { - d->m_sql->modifyInstitution(institution); - } else - throw MYMONEYEXCEPTION("unknown institution"); -} - -/** - * This method is used to update a specific transaction in the - * transaction pool of the MyMoneyFile object - * - * An exception will be thrown upon error conditions. - * - * @param transaction reference to transaction to be changed - */ -void MyMoneyDatabaseMgr::modifyTransaction(const MyMoneyTransaction& transaction) -{ - Q_D(MyMoneyDatabaseMgr); - QMap modifiedAccounts; - - // perform some checks to see that the transaction stuff is OK. For - // now we assume that - // * ids are assigned - // * the pointer to the MyMoneyFile object is not 0 - // * the date valid (must not be empty) - // * the splits must have valid account ids - - // first perform all the checks - if (transaction.id().isEmpty() -// || transaction.file() != this - || !transaction.postDate().isValid()) - throw MYMONEYEXCEPTION("invalid transaction to be modified"); - - // now check the splits - foreach (const MyMoneySplit& it_s, transaction.splits()) { - // the following lines will throw an exception if the - // account, payee or tags do not exist - account(it_s.accountId()); - if (!it_s.payeeId().isEmpty()) - payee(it_s.payeeId()); - foreach (const QString& tagId, it_s.tagIdList()) { - if (!tagId.isEmpty()) - tag(tagId); - } - } - - // new data seems to be ok. find old version of transaction - // in our pool. Throw exception if unknown. -// if(!m_transactionKeys.contains(transaction.id())) -// throw MYMONEYEXCEPTION("invalid transaction id"); - -// QString oldKey = m_transactionKeys[transaction.id()]; - QMap transactionList = d->m_sql->fetchTransactions("('" + transaction.id() + "')"); -// if(transactionList.size() != 1) -// throw MYMONEYEXCEPTION("invalid transaction key"); - - QMap::ConstIterator it_t; - -// it_t = transactionList.find(oldKey); - it_t = transactionList.constBegin(); - if (it_t == transactionList.constEnd()) - throw MYMONEYEXCEPTION("invalid transaction key"); - - d->m_sql->modifyTransaction(transaction); - - // mark all accounts referenced in old and new transaction data - // as modified - QMap accountList = d->m_sql->fetchAccounts(); - QList aList; - foreach (const MyMoneySplit& it_s, (*it_t).splits()) { - MyMoneyAccount acc = accountList[it_s.accountId()]; - acc.adjustBalance(it_s, true); - acc.touch(); -//FIXME: invalidateBalanceCache(acc.id()); - //m_accountList.modify(acc.id(), acc); - aList << acc; - //modifiedAccounts[(*it_s).accountId()] = true; - } - d->m_sql->modifyAccountList(aList); - aList.clear(); - foreach (const MyMoneySplit& it_s, transaction.splits()) { - MyMoneyAccount acc = accountList[it_s.accountId()]; - acc.adjustBalance(it_s); - acc.touch(); -//FIXME: invalidateBalanceCache(acc.id()); - //m_accountList.modify(acc.id(), acc); - aList << acc; - //modifiedAccounts[(*it_s).accountId()] = true; - } - d->m_sql->modifyAccountList(aList); - - // remove old transaction from lists -// m_sql->removeTransaction(oldKey); - - // add new transaction to lists -// QString newKey = transaction.uniqueSortKey(); -// m_sql->insertTransaction(newKey, transaction); - //m_transactionKeys.modify(transaction.id(), newKey); -} - -void MyMoneyDatabaseMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent) -{ - Q_D(MyMoneyDatabaseMgr); - if (account.accountType() == eMyMoney::Account::Type::Stock && parent.accountType() != eMyMoney::Account::Type::Investment) - throw MYMONEYEXCEPTION("Cannot move a stock acocunt into a non-investment account"); - - QStringList accountIdList; - QMap::ConstIterator oldParent; - QMap::ConstIterator newParent; - QMap::ConstIterator childAccount; - - // verify that accounts exist. If one does not, - // an exception is thrown - accountIdList << account.id() << parent.id(); - MyMoneyDatabaseMgr::account(account.id()); - MyMoneyDatabaseMgr::account(parent.id()); - - if (!account.parentAccountId().isEmpty()) { - accountIdList << account.parentAccountId(); - } - - startTransaction(); - QMap accountList = d->m_sql->fetchAccounts(accountIdList, true); - - if (!account.parentAccountId().isEmpty()) { - MyMoneyDatabaseMgr::account(account.parentAccountId()); - oldParent = accountList.constFind(account.parentAccountId()); - } - - newParent = accountList.constFind(parent.id()); - childAccount = accountList.constFind(account.id()); - - MyMoneyAccount acc; - QList aList; - if (!account.parentAccountId().isEmpty()) { - acc = (*oldParent); - acc.removeAccountId(account.id()); - aList << acc; - } - - parent = (*newParent); - parent.addAccountId(account.id()); - - account = (*childAccount); - account.setParentAccountId(parent.id()); - - aList << parent << account; - d->m_sql->modifyAccountList(aList); - commitTransaction(); -} - -void MyMoneyDatabaseMgr::removeTransaction(const MyMoneyTransaction& transaction) -{ - Q_D(MyMoneyDatabaseMgr); - QMap modifiedAccounts; - - // first perform all the checks - if (transaction.id().isEmpty()) - throw MYMONEYEXCEPTION("invalid transaction to be deleted"); - - QMap::ConstIterator it_k; - QMap::ConstIterator it_t; - -// it_k = m_transactionKeys.find(transaction.id()); -// if(it_k == m_transactionKeys.end()) -// throw MYMONEYEXCEPTION("invalid transaction to be deleted"); - - QMap transactionList = d->m_sql->fetchTransactions("('" + QString(transaction.id()) + "')"); -// it_t = transactionList.find(*it_k); - it_t = transactionList.constBegin(); - if (it_t == transactionList.constEnd()) - throw MYMONEYEXCEPTION("invalid transaction key"); - - // scan the splits and collect all accounts that need - // to be updated after the removal of this transaction - QMap accountList = d->m_sql->fetchAccounts(); - QList aList; - foreach (const MyMoneySplit& it_s, (*it_t).splits()) { - MyMoneyAccount acc = accountList[it_s.accountId()]; -// modifiedAccounts[(*it_s).accountId()] = true; - acc.adjustBalance(it_s, true); - acc.touch(); - aList << acc; -//FIXME: invalidateBalanceCache(acc.id()); - } - d->m_sql->modifyAccountList(aList); - - // FIXME: check if any split is frozen and throw exception - - // remove the transaction from the two lists - //m_transactionList.remove(*it_k); -// m_transactionKeys.remove(transaction.id()); - - // mark file as changed - d->m_sql->removeTransaction(transaction); -} - -uint MyMoneyDatabaseMgr::transactionCount(const QString& account) const -{ - Q_D(const MyMoneyDatabaseMgr); - return (d->m_sql->transactionCount(account)); -} - -QMap MyMoneyDatabaseMgr::transactionCountMap() const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap retval; - QHash hash = d->m_sql->transactionCountMap(); - - for (QHash::ConstIterator i = hash.constBegin(); - i != hash.constEnd(); ++i) { - retval[i.key()] = i.value(); - } - return retval; -} - -QList MyMoneyDatabaseMgr::transactionList(MyMoneyTransactionFilter& filter) const -{ - QList list; - transactionList(list, filter); - return list; -} - -void MyMoneyDatabaseMgr::transactionList(QList& list, MyMoneyTransactionFilter& filter) const -{ - Q_D(const MyMoneyDatabaseMgr); - list.clear(); - - try { - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - list = d->m_sql->fetchTransactions(filter).values(); - } - } catch (const MyMoneyException &) { - throw; - } -} - -void MyMoneyDatabaseMgr::transactionList(QList >& list, MyMoneyTransactionFilter& filter) const -{ - Q_D(const MyMoneyDatabaseMgr); - list.clear(); - MyMoneyMap transactionList; - try { - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - transactionList = d->m_sql->fetchTransactions(filter); - } - } catch (const MyMoneyException &) { - throw; - } - - for (const auto& transaction : transactionList) - for (const auto& split : filter.matchingSplits(transaction)) - list.append(qMakePair(transaction, split)); -} - -void MyMoneyDatabaseMgr::removeAccount(const MyMoneyAccount& account) -{ - Q_D(MyMoneyDatabaseMgr); - MyMoneyAccount parent; - - // check that the account and it's parent exist - // this will throw an exception if the id is unknown - MyMoneyDatabaseMgr::account(account.id()); - parent = MyMoneyDatabaseMgr::account(account.parentAccountId()); - - // check that it's not one of the standard account groups - if (isStandardAccount(account.id())) - throw MYMONEYEXCEPTION("Unable to remove the standard account groups"); - - if (hasActiveSplits(account.id())) { - throw MYMONEYEXCEPTION("Unable to remove account with active splits"); - } - - // re-parent all sub-ordinate accounts to the parent of the account - // to be deleted. First round check that all accounts exist, second - // round do the re-parenting. - foreach (const QString& it, account.accountList()) { - MyMoneyDatabaseMgr::account(it); - } - - // if one of the accounts did not exist, an exception had been - // thrown and we would not make it until here. - - QStringList accountIdList; - accountIdList << parent.id() << account.id(); - startTransaction(); - QMap accountList = d->m_sql->fetchAccounts(accountIdList, true); - - QMap::ConstIterator it_a; - QMap::ConstIterator it_p; - - // locate the account in the file global pool - - it_a = accountList.constFind(account.id()); - if (it_a == accountList.constEnd()) - throw MYMONEYEXCEPTION("Internal error: account not found in list"); - - it_p = accountList.constFind(parent.id()); - if (it_p == accountList.constEnd()) - throw MYMONEYEXCEPTION("Internal error: parent account not found in list"); - - if (!account.institutionId().isEmpty()) - throw MYMONEYEXCEPTION("Cannot remove account still attached to an institution"); - - // FIXME: check referential integrity for the account to be removed - - // check if the new info is based on the old one. - // this is the case, when the file and the id - // as well as the type are equal. - if ((*it_a).id() == account.id() - && (*it_a).accountType() == account.accountType()) { - - // second round over sub-ordinate accounts: do re-parenting - // but only if the list contains at least one entry - // FIXME: move this logic to MyMoneyFile - if ((*it_a).accountList().count() > 0) { - foreach (const QString& it, (*it_a).accountList()) { - MyMoneyAccount acc(MyMoneyDatabaseMgr::account(it)); - reparentAccount(acc, parent);//, false); - } - } - // remove account from parent's list - parent.removeAccountId(account.id()); - d->m_sql->modifyAccount(parent); - - // remove account from the global account pool - //m_accountList.remove(account.id()); - - // remove from balance list -//FIXME: m_balanceCache.remove(account.id()); -//FIXME: invalidateBalanceCache(parent.id()); - - d->m_sql->removeAccount(account); - } - commitTransaction(); -} - -void MyMoneyDatabaseMgr::removeInstitution(const MyMoneyInstitution& institution) -{ - Q_D(MyMoneyDatabaseMgr); - QMap institutionList = d->m_sql->fetchInstitutions(QStringList(institution.id())); - QMap::ConstIterator it_i; - - it_i = institutionList.constFind(institution.id()); - if (it_i != institutionList.constEnd()) { - // mark file as changed - d->m_sql->removeInstitution(institution); - } else - throw MYMONEYEXCEPTION("invalid institution"); -} - -MyMoneyTransaction MyMoneyDatabaseMgr::transaction(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - // get the full key of this transaction, throw exception - // if it's invalid (unknown) - //if(!m_transactionKeys.contains(id)) - // throw MYMONEYEXCEPTION("invalid transaction id"); - - // check if this key is in the list, throw exception if not - //QString key = m_transactionKeys[id]; - QMap transactionList = d->m_sql->fetchTransactions("('" + QString(id) + "')"); - - //there should only be one transaction in the map, if it was found, so check the size of the map - //return the first element. - //if(!transactionList.contains(key)) - if (!transactionList.size()) - throw MYMONEYEXCEPTION("invalid transaction key"); - - return transactionList.begin().value(); -} - -MyMoneyMoney MyMoneyDatabaseMgr::balance(const QString& id, const QDate& date) const -{ - Q_D(const MyMoneyDatabaseMgr); - MyMoneyMoney result; - QStringList idList; - idList.append(id); - QMap tempMap = d->m_sql->fetchBalance(idList, date); - - QMap::ConstIterator returnValue = tempMap.constFind(id); - if (returnValue != tempMap.constEnd()) { - result = returnValue.value(); - return result; - } - -//DEBUG - QDate date_(date); - //if (date_ == QDate()) date_ = QDate::currentDate(); -// END DEBUG - -// MyMoneyAccount acc; - QMap accountList = d->m_sql->fetchAccounts(/*QString(id)*/); - //QMap::const_iterator accpos = accountList.find(id); - if (date_ != QDate()) qDebug("request balance for %s at %s", qPrintable(id), qPrintable(date_.toString(Qt::ISODate))); -// if(!date_.isValid() && MyMoneyFile::instance()->account(id).accountType() != eMyMoney::Account::Type::Stock) { -// if(accountList.find(id) != accountList.end()) -// return accountList[id].balance(); -// return MyMoneyMoney(0); -// } - if (/*m_balanceCache[id].valid == false || date != m_balanceCacheDate) || */ d->m_sql) { - QMap balances; - QMap::ConstIterator it_b; -//FIXME: if (date != m_balanceCacheDate) { -//FIXME: m_balanceCache.clear(); -//FIXME: m_balanceCacheDate = date; -//FIXME: } - - MyMoneyTransactionFilter filter; - filter.addAccount(id); - filter.setDateFilter(QDate(), date_); - filter.setReportAllSplits(false); - QList list = transactionList(filter); - - foreach (const MyMoneyTransaction& it_t, list) { - foreach (const MyMoneySplit& it_s, it_t.splits()) { - const QString aid = it_s.accountId(); - if (it_s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) { - balances[aid] *= it_s.shares(); - } else { - balances[aid] += it_s.value(it_t.commodity(), accountList[aid].currencyId()); - } - } - } - - // fill the found balances into the cache -//FIXME: for(it_b = balances.begin(); it_b != balances.end(); ++it_b) { -//FIXME: MyMoneyBalanceCacheItem balance(*it_b); -//FIXME: m_balanceCache[it_b.key()] = balance; -//FIXME: } - - // fill all accounts w/o transactions to zero -// if (m_sql != 0) { -// QMap::ConstIterator it_a; -// for(it_a = m_accountList.begin(); it_a != m_accountList.end(); ++it_a) { -//FIXME: if(m_balanceCache[(*it_a).id()].valid == false) { -//FIXME: MyMoneyBalanceCacheItem balance(MyMoneyMoney(0,1)); -//FIXME: m_balanceCache[(*it_a).id()] = balance; -//FIXME: } -// } -// } - - result = balances[id]; - - } - -//FIXME: if(m_balanceCache[id].valid == true) -//FIXME: result = m_balanceCache[id].balance; -//FIXME: else -//FIXME: qDebug("Cache mishit should never happen at this point"); - - return result; -} - -MyMoneyTransaction MyMoneyDatabaseMgr::transaction(const QString& account, const int idx) const -{ - Q_D(const MyMoneyDatabaseMgr); - /* removed with MyMoneyAccount::Transaction - QMap::ConstIterator acc; - - // find account object in list, throw exception if unknown - acc = m_accountList.find(account); - if(acc == m_accountList.end()) - throw MYMONEYEXCEPTION("unknown account id"); - - // get the transaction info from the account - MyMoneyAccount::Transaction t = (*acc).transaction(idx); - - // return the transaction, throw exception if not found - return transaction(t.transactionID()); - */ - - // new implementation if the above code does not work anymore - QList list; - //MyMoneyAccount acc = m_accountList[account]; - MyMoneyAccount acc = d->m_sql->fetchAccounts(QStringList(account))[account]; - MyMoneyTransactionFilter filter; - - if (acc.accountGroup() == eMyMoney::Account::Type::Income - || acc.accountGroup() == eMyMoney::Account::Type::Expense) - filter.addCategory(account); - else - filter.addAccount(account); - - transactionList(list, filter); - if (idx < 0 || idx >= static_cast(list.count())) - throw MYMONEYEXCEPTION("Unknown idx for transaction"); - - return transaction(list[idx].id()); -} - -uint MyMoneyDatabaseMgr::institutionCount() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getRecCount("kmmInstitutions"); -} - -void MyMoneyDatabaseMgr::accountList(QList& list) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap accountList; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - accountList = d->m_sql->fetchAccounts(); - } - QMap::ConstIterator it; - QMap::ConstIterator accEnd = accountList.constEnd(); - for (it = accountList.constBegin(); it != accEnd; ++it) { - if (!isStandardAccount((*it).id())) { - list.append(*it); - } - } -} - -MyMoneyAccount MyMoneyDatabaseMgr::liability() const -{ - return MyMoneyFile::instance()->account(stdAccNames[stdAccLiability]); -} - -MyMoneyAccount MyMoneyDatabaseMgr::asset() const -{ - return MyMoneyFile::instance()->account(stdAccNames[stdAccAsset]); -} - -MyMoneyAccount MyMoneyDatabaseMgr::expense() const -{ - return MyMoneyFile::instance()->account(stdAccNames[stdAccExpense]); -} - -MyMoneyAccount MyMoneyDatabaseMgr::income() const -{ - return MyMoneyFile::instance()->account(stdAccNames[stdAccIncome]); -} - -MyMoneyAccount MyMoneyDatabaseMgr::equity() const -{ - return MyMoneyFile::instance()->account(stdAccNames[stdAccEquity]); -} - -void MyMoneyDatabaseMgr::addSecurity(MyMoneySecurity& security) -{ - Q_D(MyMoneyDatabaseMgr); - // create the account - try { - startTransaction(); - MyMoneySecurity newSecurity(nextSecurityID(), security); - - d->m_sql->addSecurity(newSecurity); - security = newSecurity; - commitTransaction(); - } catch (...) { - rollbackTransaction(); - throw; - } -} - -void MyMoneyDatabaseMgr::modifySecurity(const MyMoneySecurity& security) -{ - Q_D(MyMoneyDatabaseMgr); - QMap securitiesList = d->m_sql->fetchSecurities(QStringList(security.id()), true); - QMap::ConstIterator it; - - it = securitiesList.constFind(security.id()); - if (it == securitiesList.constEnd()) { - QString msg = "Unknown security '"; - msg += security.id() + "' during modifySecurity()"; - throw MYMONEYEXCEPTION(msg); - } - - d->m_sql->modifySecurity(security); -} - -void MyMoneyDatabaseMgr::removeSecurity(const MyMoneySecurity& security) -{ - Q_D(MyMoneyDatabaseMgr); - QMap securitiesList = d->m_sql->fetchSecurities(QStringList(security.id())); - QMap::ConstIterator it; - - // FIXME: check referential integrity - - it = securitiesList.constFind(security.id()); - if (it == securitiesList.constEnd()) { - QString msg = "Unknown security '"; - msg += security.id() + "' during removeSecurity()"; - throw MYMONEYEXCEPTION(msg); - } - - d->m_sql->removeSecurity(security); -} - -MyMoneySecurity MyMoneyDatabaseMgr::security(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap securitiesList = d->m_sql->fetchSecurities(QStringList(id)); - QMap::ConstIterator it = securitiesList.constFind(id); - if (it != securitiesList.constEnd()) { - return it.value(); - } - - return MyMoneySecurity(); -} - -QList MyMoneyDatabaseMgr::securityList() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->fetchSecurities().values(); -} - -void MyMoneyDatabaseMgr::addPrice(const MyMoneyPrice& price) -{ - Q_D(MyMoneyDatabaseMgr); - MyMoneyPriceEntries::ConstIterator it; - MyMoneyPriceList priceList = d->m_sql->fetchPrices(); - it = priceList[MyMoneySecurityPair(price.from(), price.to())].constFind(price.date()); - // do not replace, if the information did not change. - if (it != priceList[MyMoneySecurityPair(price.from(), price.to())].constEnd()) { - if ((*it).rate((*it).to()) == price.rate(price.to()) - && (*it).source() == price.source()) - return; - } - - d->m_sql->addPrice(price); -} - -void MyMoneyDatabaseMgr::removePrice(const MyMoneyPrice& price) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->removePrice(price); -} - -MyMoneyPrice MyMoneyDatabaseMgr::price(const QString& fromId, const QString& toId, const QDate& _date, bool exactDate) const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->fetchSinglePrice(fromId, toId, _date, exactDate); -} - -MyMoneyPriceList MyMoneyDatabaseMgr::priceList() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->fetchPrices(); -} - -void MyMoneyDatabaseMgr::addSchedule(MyMoneySchedule& sched) -{ - Q_D(MyMoneyDatabaseMgr); - // first perform all the checks - if (!sched.id().isEmpty()) - throw MYMONEYEXCEPTION("schedule already contains an id"); - - // The following will throw an exception when it fails - sched.validate(false); - - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - try { - startTransaction(); - sched = MyMoneySchedule(nextScheduleID(), sched); - - d->m_sql->addSchedule(sched); - commitTransaction(); - } catch (...) { - rollbackTransaction(); - throw; - } - } -} - -void MyMoneyDatabaseMgr::modifySchedule(const MyMoneySchedule& sched) -{ - Q_D(MyMoneyDatabaseMgr); - QMap scheduleList = d->m_sql->fetchSchedules(QStringList(sched.id())); - QMap::ConstIterator it; - - it = scheduleList.constFind(sched.id()); - if (it == scheduleList.constEnd()) { - QString msg = "Unknown schedule '" + sched.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - d->m_sql->modifySchedule(sched); -} - -void MyMoneyDatabaseMgr::removeSchedule(const MyMoneySchedule& sched) -{ - Q_D(MyMoneyDatabaseMgr); - QMap scheduleList = d->m_sql->fetchSchedules(QStringList(sched.id())); - QMap::ConstIterator it; - - it = scheduleList.constFind(sched.id()); - if (it == scheduleList.constEnd()) { - QString msg = "Unknown schedule '" + sched.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - // FIXME: check referential integrity for loan accounts - - d->m_sql->removeSchedule(sched); -} - -MyMoneySchedule MyMoneyDatabaseMgr::schedule(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap scheduleList = d->m_sql->fetchSchedules(QStringList(id)); - QMap::ConstIterator pos; - - // locate the schedule and if present, return it's data - pos = scheduleList.constFind(id); - if (pos != scheduleList.constEnd()) - return (*pos); - - // throw an exception, if it does not exist - QString msg = "Unknown schedule id '" + id + '\''; - throw MYMONEYEXCEPTION(msg); -} - -QList MyMoneyDatabaseMgr::scheduleList(const QString& accountId, - eMyMoney::Schedule::Type type, - eMyMoney::Schedule::Occurrence occurrence, - eMyMoney::Schedule::PaymentType paymentType, - const QDate& startDate, - const QDate& endDate, - bool overdue) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap scheduleList; - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - scheduleList = d->m_sql->fetchSchedules(); - } - QMap::ConstIterator pos; - QList list; - - // qDebug("scheduleList()"); - - for (pos = scheduleList.constBegin(); pos != scheduleList.constEnd(); ++pos) { - // qDebug(" '%s'", (*pos).id().data()); - - if (type != eMyMoney::Schedule::Type::Any) { - if (type != (*pos).type()) { - continue; - } - } - - if (occurrence != eMyMoney::Schedule::Occurrence::Any) { - if (occurrence != (*pos).occurrence()) { - continue; - } - } - - if (paymentType != eMyMoney::Schedule::PaymentType::Any) { - if (paymentType != (*pos).paymentType()) { - continue; - } - } - - if (!accountId.isEmpty()) { - MyMoneyTransaction t = (*pos).transaction(); - QList::ConstIterator it; - QList splits; - splits = t.splits(); - for (it = splits.constBegin(); it != splits.constEnd(); ++it) { - if ((*it).accountId() == accountId) - break; - } - if (it == splits.constEnd()) { - continue; - } - } - - if (startDate.isValid() && endDate.isValid()) { - if ((*pos).paymentDates(startDate, endDate).count() == 0) { - continue; - } - } - - if (startDate.isValid() && !endDate.isValid()) { - if (!(*pos).nextPayment(startDate.addDays(-1)).isValid()) { - continue; - } - } - - if (!startDate.isValid() && endDate.isValid()) { - if ((*pos).startDate() > endDate) { - continue; - } - } - - if (overdue) { - if (!(*pos).isOverdue()) - continue; - /* - QDate nextPayment = (*pos).nextPayment((*pos).lastPayment()); - if(!nextPayment.isValid()) - continue; - if(nextPayment >= QDate::currentDate()) - continue; - */ - } - - // qDebug("Adding '%s'", (*pos).name().latin1()); - list << *pos; - } - return list; -} - -QList MyMoneyDatabaseMgr::scheduleListEx(int scheduleTypes, - int scheduleOcurrences, - int schedulePaymentTypes, - QDate startDate, - const QStringList& accounts) const -{ - Q_D(const MyMoneyDatabaseMgr); -// qDebug("scheduleListEx"); - QMap scheduleList = d->m_sql->fetchSchedules(); - QMap::ConstIterator pos; - QList list; - - if (!startDate.isValid()) - return list; - - for (pos = scheduleList.constBegin(); pos != scheduleList.constEnd(); ++pos) { - if (scheduleTypes && !(scheduleTypes & (int)(*pos).type())) - continue; - - if (scheduleOcurrences && !(scheduleOcurrences & (int)(*pos).occurrence())) - continue; - - if (schedulePaymentTypes && !(schedulePaymentTypes & (int)(*pos).paymentType())) - continue; - - if ((*pos).paymentDates(startDate, startDate).count() == 0) - continue; - - if ((*pos).isFinished()) - continue; - - if ((*pos).hasRecordedPayment(startDate)) - continue; - - if (accounts.count() > 0) { - if (accounts.contains((*pos).account().id())) - continue; - } - -// qDebug("\tAdding '%s'", (*pos).name().latin1()); - list << *pos; - } - - return list; -} - -void MyMoneyDatabaseMgr::addCurrency(const MyMoneySecurity& currency) -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - QMap currencyList = d->m_sql->fetchCurrencies(QStringList(currency.id())); - QMap::ConstIterator it; - - it = currencyList.constFind(currency.id()); - if (it != currencyList.constEnd()) { - throw MYMONEYEXCEPTION(i18n("Cannot add currency with existing id %1", currency.id())); - } - - d->m_sql->addCurrency(currency); - } -} - -void MyMoneyDatabaseMgr::modifyCurrency(const MyMoneySecurity& currency) -{ - Q_D(MyMoneyDatabaseMgr); - QMap currencyList = d->m_sql->fetchCurrencies(QStringList(currency.id())); - QMap::ConstIterator it; - - it = currencyList.constFind(currency.id()); - if (it == currencyList.constEnd()) { - throw MYMONEYEXCEPTION(i18n("Cannot modify currency with unknown id %1", currency.id())); - } - - d->m_sql->modifyCurrency(currency); -} - -void MyMoneyDatabaseMgr::removeCurrency(const MyMoneySecurity& currency) -{ - Q_D(MyMoneyDatabaseMgr); - QMap currencyList = d->m_sql->fetchCurrencies(QStringList(currency.id())); - QMap::ConstIterator it; - - // FIXME: check referential integrity - - it = currencyList.constFind(currency.id()); - if (it == currencyList.constEnd()) { - throw MYMONEYEXCEPTION(i18n("Cannot remove currency with unknown id %1", currency.id())); - } - - d->m_sql->removeCurrency(currency); -} - -MyMoneySecurity MyMoneyDatabaseMgr::currency(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - if (id.isEmpty()) { - - } - QMap currencyList = d->m_sql->fetchCurrencies(QStringList(id)); - QMap::ConstIterator it; - - it = currencyList.constFind(id); - if (it == currencyList.constEnd()) { - throw MYMONEYEXCEPTION(i18n("Cannot retrieve currency with unknown id '%1'", id)); - } - - return *it; -} - -QList MyMoneyDatabaseMgr::currencyList() const -{ - Q_D(const MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->fetchCurrencies().values(); - } else { - return QList (); - } -} - -QList MyMoneyDatabaseMgr::reportList() const -{ - Q_D(const MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->fetchReports().values(); - } else { - return QList (); - } -} - -void MyMoneyDatabaseMgr::addReport(MyMoneyReport& report) -{ - Q_D(MyMoneyDatabaseMgr); - if (!report.id().isEmpty()) - throw MYMONEYEXCEPTION("transaction already contains an id"); - - d->m_sql->addReport(MyMoneyReport(nextReportID(), report)); -} - -void MyMoneyDatabaseMgr::modifyReport(const MyMoneyReport& report) -{ - Q_D(MyMoneyDatabaseMgr); - QMap reportList = d->m_sql->fetchReports(QStringList(report.id())); - QMap::ConstIterator it; - - it = reportList.constFind(report.id()); - if (it == reportList.constEnd()) { - QString msg = "Unknown report '" + report.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - d->m_sql->modifyReport(report); -} - -uint MyMoneyDatabaseMgr::countReports() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getRecCount("kmmReports"); -} - -MyMoneyReport MyMoneyDatabaseMgr::report(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->fetchReports(QStringList(id))[id]; -} - -void MyMoneyDatabaseMgr::removeReport(const MyMoneyReport& report) -{ - Q_D(MyMoneyDatabaseMgr); - QMap reportList = d->m_sql->fetchReports(QStringList(report.id())); - QMap::ConstIterator it; - - it = reportList.constFind(report.id()); - if (it == reportList.constEnd()) { - QString msg = "Unknown report '" + report.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - - d->m_sql->removeReport(report); -} - -QList MyMoneyDatabaseMgr::budgetList() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->fetchBudgets().values(); -} - -void MyMoneyDatabaseMgr::addBudget(MyMoneyBudget& budget) -{ - Q_D(MyMoneyDatabaseMgr); - MyMoneyBudget newBudget(nextBudgetID(), budget); - d->m_sql->addBudget(newBudget); -} - -MyMoneyBudget MyMoneyDatabaseMgr::budgetByName(const QString& budget) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap budgets = d->m_sql->fetchBudgets(); - QMap::ConstIterator it_p; - - for (it_p = budgets.constBegin(); it_p != budgets.constEnd(); ++it_p) { - if ((*it_p).name() == budget) { - return *it_p; - } - } - - throw MYMONEYEXCEPTION("Unknown budget '" + budget + '\''); -} - -void MyMoneyDatabaseMgr::modifyBudget(const MyMoneyBudget& budget) -{ - Q_D(MyMoneyDatabaseMgr); - //QMap::ConstIterator it; - - //it = m_budgetList.find(budget.id()); - //if(it == m_budgetList.end()) { - // QString msg = "Unknown budget '" + budget.id() + '\''; - // throw MYMONEYEXCEPTION(msg); - //} - //m_budgetList.modify(budget.id(), budget); - - startTransaction(); - if (d->m_sql->fetchBudgets(QStringList(budget.id()), true).empty()) { - QString msg = "Unknown budget '" + budget.id() + '\''; - throw MYMONEYEXCEPTION(msg); - } - d->m_sql->modifyBudget(budget); - commitTransaction(); -} - -uint MyMoneyDatabaseMgr::countBudgets() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getRecCount("kmmBudgetConfig"); -} - -MyMoneyBudget MyMoneyDatabaseMgr::budget(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->fetchBudgets(QStringList(id))[id]; -} - -void MyMoneyDatabaseMgr::removeBudget(const MyMoneyBudget& budget) -{ - Q_D(MyMoneyDatabaseMgr); -// QMap::ConstIterator it; -// -// it = m_budgetList.find(budget.id()); -// if(it == m_budgetList.end()) { -// QString msg = "Unknown budget '" + budget.id() + '\''; -// throw MYMONEYEXCEPTION(msg); -// } -// - d->m_sql->removeBudget(budget); -} - -void MyMoneyDatabaseMgr::clearCache() -{ - //m_balanceCache.clear(); -} - -class isReferencedHelper -{ -public: - isReferencedHelper(const QString& id) - : m_id(id) {} - - inline bool operator()(const MyMoneyObject& obj) const { - return obj.hasReferenceTo(m_id); - } - -private: - QString m_id; -}; - -bool MyMoneyDatabaseMgr::isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const -{ - Q_D(const MyMoneyDatabaseMgr); - Q_ASSERT(skipCheck.count() == (int)Reference::Count); - - const auto& id = obj.id(); - - // FIXME optimize the list of objects we have to checks - // with a bit of knowledge of the internal structure, we - // could optimize the number of objects we check for references - - // Scan all engine objects for a reference - if (!skipCheck.testBit((int)Reference::Transaction)) { - auto skipTransactions = false; - MyMoneyTransactionFilter f; - if (typeid(obj) == typeid(MyMoneyAccount)) { - f.addAccount(id); - } else if (typeid(obj) == typeid(MyMoneyCategory)) { - f.addCategory(id); - } else if (typeid(obj) == typeid(MyMoneyPayee)) { - f.addPayee(id); - } // if it's anything else, I guess we just read everything - //FIXME: correction, transactions can only have a reference to an account or payee, - // so, read nothing. - else { - skipTransactions = true; - } - if (! skipTransactions) { - //QMap transactionList = m_sql->fetchTransactions(f); - //rc = (transactionList.end() != std::find_if(transactionList.begin(), transactionList.end(), isReferencedHelper(id))); - //if (rc != m_sql->isReferencedByTransaction(obj.id())) - // qDebug ("Transaction match inconsistency."); - if (d->m_sql->isReferencedByTransaction(id)) - return true; - } - } - - if (!skipCheck.testBit((int)Reference::Account)) { - QList accountList; - MyMoneyFile::instance()->accountList(accountList); - foreach (const auto it, accountList) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Institution)) { - QList institutionList; - MyMoneyFile::instance()->institutionList(institutionList); - foreach (const auto it, institutionList) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Payee)) { - foreach (const auto it, MyMoneyFile::instance()->payeeList()) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Tag)) { - foreach (const auto it, MyMoneyFile::instance()->tagList()) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Report)) { - foreach (const auto it, d->m_sql->fetchReports()) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Budget)) { - foreach (const auto it, d->m_sql->fetchBudgets()) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Schedule)) { - foreach (const auto it, d->m_sql->fetchSchedules()) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Security)) { - foreach (const auto it, MyMoneyFile::instance()->securityList()) - if (it.hasReferenceTo(id)) - return true; - } - if (!skipCheck.testBit((int)Reference::Currency)) { - const auto currencyList = d->m_sql->fetchCurrencies().values(); - // above line cannot go directly here because m_sql->fetchCurrencies() will return temporary object which will get destructed before .values() - foreach (const auto it, currencyList) - if (it.hasReferenceTo(id)) - return true; - } - // within the pricelist we don't have to scan each entry. Checking the QPair - // members of the MyMoneySecurityPair is enough as they are identical to the - // two security ids - if (!skipCheck.testBit((int)Reference::Price)) { - const auto priceList = d->m_sql->fetchPrices(); - for (auto it_pr = priceList.begin(); it_pr != priceList.end(); ++it_pr) { - if ((it_pr.key().first == id) || (it_pr.key().second == id)) - return true; - } - } - return false; -} - -void MyMoneyDatabaseMgr::close() -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - d->m_sql->close(true); - d->m_sql = 0; - } -} - -void MyMoneyDatabaseMgr::startTransaction() -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - d->m_sql->startCommitUnit("databasetransaction"); - } -} - -bool MyMoneyDatabaseMgr::commitTransaction() -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->endCommitUnit("databasetransaction"); - } - return false; -} - -void MyMoneyDatabaseMgr::rollbackTransaction() -{ - Q_D(MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - d->m_sql->cancelCommitUnit("databasetransaction"); - } -} - -void MyMoneyDatabaseMgr::setCreationDate(const QDate& val) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_creationDate = val; -} - -QExplicitlySharedDataPointer MyMoneyDatabaseMgr::connectToDatabase(const QUrl &url) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql = new MyMoneyStorageSql(this, url); - return d->m_sql; -} - -void MyMoneyDatabaseMgr::fillStorage() -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->fillStorage(); -} - -void MyMoneyDatabaseMgr::setLastModificationDate(const QDate& val) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_lastModificationDate = val; -} - -bool MyMoneyDatabaseMgr::isDuplicateTransaction(const QString& /*id*/) const -{ - //FIXME: figure out the real id from the key and check the DB. -//return m_transactionKeys.contains(id); - return false; -} - -void MyMoneyDatabaseMgr::loadAccounts(const QMap& /*map*/) -{ -// m_accountList = map; -//FIXME: update the database. -// startTransaction -// DELETE FROM kmmAccounts -// for each account in the map -// m_sql->addAccount(...) -// commitTransaction -// on error, rollbackTransaction -} - -void MyMoneyDatabaseMgr::loadTransactions(const QMap& /*map*/) -{ -// m_transactionList = map; -//FIXME: update the database. - -// // now fill the key map -// QMap keys; -// QMap::ConstIterator it_t; -// for(it_t = map.begin(); it_t != map.end(); ++it_t) { -// keys[(*it_t).id()] = it_t.key(); -// } -// m_transactionKeys = keys; -} - -void MyMoneyDatabaseMgr::loadInstitutions(const QMap& /*map*/) -{ -// m_institutionList = map; -//FIXME: update the database. - -// // now fill the key map -// QMap keys; -// QMap::ConstIterator it_t; -// for(it_t = map.begin(); it_t != map.end(); ++it_t) { -// keys[(*it_t).id()] = it_t.key(); -// } -// m_transactionKeys = keys; -} - -void MyMoneyDatabaseMgr::loadPayees(const QMap& /*map*/) -{ -// m_payeeList = map; -} - -void MyMoneyDatabaseMgr::loadTags(const QMap& /*map*/) -{ -// m_tagList = map; -} - -void MyMoneyDatabaseMgr::loadSchedules(const QMap& /*map*/) -{ -// m_scheduleList = map; -} - -void MyMoneyDatabaseMgr::loadSecurities(const QMap& /*map*/) -{ -// m_securitiesList = map; -} - -void MyMoneyDatabaseMgr::loadCurrencies(const QMap& /*map*/) -{ -// m_currencyList = map; -//FIXME: update the database. -// startTransaction -// DELETE FROM kmmBudgetConfig -// for each budget in the map -// m_sql->addBudget(...) -// commitTransaction -// on error, rollbackTransaction -} - -void MyMoneyDatabaseMgr::loadReports(const QMap& /*reports*/) -{ -// m_reportList = reports; -//FIXME: update the database. -// startTransaction -// DELETE FROM kmmBudgetConfig -// for each budget in the map -// m_sql->addBudget(...) -// commitTransaction -// on error, rollbackTransaction -} - -void MyMoneyDatabaseMgr::loadBudgets(const QMap& /*budgets*/) -{ -// m_budgetList = budgets; -//FIXME: update the database. -// startTransaction -// DELETE FROM kmmBudgetConfig -// for each budget in the map -// m_sql->addBudget(...) -// commitTransaction -// on error, rollbackTransaction -} - -void MyMoneyDatabaseMgr::loadPrices(const MyMoneyPriceList& list) -{ - Q_UNUSED(list); -} - -void MyMoneyDatabaseMgr::loadOnlineJobs(const QMap< QString, onlineJob >& onlineJobs) -{ - Q_UNUSED(onlineJobs); -} - -ulong MyMoneyDatabaseMgr::accountId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextAccountId() - 1; -} - -ulong MyMoneyDatabaseMgr::transactionId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextTransactionId() - 1; -} - -ulong MyMoneyDatabaseMgr::payeeId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextPayeeId() - 1; -} - -ulong MyMoneyDatabaseMgr::tagId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextTagId() - 1; -} - -ulong MyMoneyDatabaseMgr::institutionId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextInstitutionId() - 1; -} - -ulong MyMoneyDatabaseMgr::scheduleId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextScheduleId() - 1; -} - -ulong MyMoneyDatabaseMgr::securityId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextSecurityId() - 1; -} - -ulong MyMoneyDatabaseMgr::reportId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextReportId() - 1; -} - -ulong MyMoneyDatabaseMgr::budgetId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextBudgetId() - 1; -} - -ulong MyMoneyDatabaseMgr::onlineJobId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextOnlineJobId() - 1; -} - -ulong MyMoneyDatabaseMgr::payeeIdentifierId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextPayeeIdentifierId() - 1; -} - -void MyMoneyDatabaseMgr::loadAccountId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadAccountId(id); -} - -void MyMoneyDatabaseMgr::loadTransactionId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadTransactionId(id); -} - -void MyMoneyDatabaseMgr::loadPayeeId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadPayeeId(id); -} - -void MyMoneyDatabaseMgr::loadTagId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadTagId(id); -} - -void MyMoneyDatabaseMgr::loadInstitutionId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadInstitutionId(id); -} - -void MyMoneyDatabaseMgr::loadScheduleId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadScheduleId(id); -} - -void MyMoneyDatabaseMgr::loadSecurityId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadSecurityId(id); -} - -void MyMoneyDatabaseMgr::loadReportId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadReportId(id); -} - -void MyMoneyDatabaseMgr::loadBudgetId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadBudgetId(id); -} - -void MyMoneyDatabaseMgr::loadOnlineJobId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadOnlineJobId(id); -} - -void MyMoneyDatabaseMgr::loadPayeeIdentifierId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadPayeeIdentifierId(id); -} - -void MyMoneyDatabaseMgr::loadCostCenterId(ulong id) -{ - Q_D(MyMoneyDatabaseMgr); - d->m_sql->loadAccountId(id); -} - -void MyMoneyDatabaseMgr::rebuildAccountBalances() -{ - Q_D(MyMoneyDatabaseMgr); - startTransaction(); - QMap accountMap = d->m_sql->fetchAccounts(QStringList(), true); - - QMap balanceMap = d->m_sql->fetchBalance(accountMap.keys(), QDate()); - - for (QMap::const_iterator it_b = balanceMap.constBegin(); - it_b != balanceMap.constEnd(); ++it_b) { - accountMap[it_b.key()].setBalance(it_b.value()); - } - - QList aList; - for (QMap::const_iterator it_a = accountMap.constBegin(); - it_a != accountMap.constEnd(); ++it_a) { - aList << it_a.value(); - } - d->m_sql->modifyAccountList(aList); - - commitTransaction(); -} - -QList< MyMoneyCostCenter > MyMoneyDatabaseMgr::costCenterList() const -{ - Q_D(const MyMoneyDatabaseMgr); - if (d->m_sql) { - if (! d->m_sql->isOpen())((QSqlDatabase*)(d->m_sql.data()))->open(); - return d->m_sql->fetchCostCenters().values(); - } - return QList (); -} - -void MyMoneyDatabaseMgr::loadCostCenters(const QMap< QString, MyMoneyCostCenter >& costCenters) -{ - Q_UNUSED(costCenters); -} - -ulong MyMoneyDatabaseMgr::costCenterId() const -{ - Q_D(const MyMoneyDatabaseMgr); - return d->m_sql->getNextCostCenterId() - 1; -} - -MyMoneyCostCenter MyMoneyDatabaseMgr::costCenter(const QString& id) const -{ - Q_D(const MyMoneyDatabaseMgr); - QMap::ConstIterator it; - QMap costCenterList = d->m_sql->fetchCostCenters(QStringList(id)); - it = costCenterList.constFind(id); - if (it == costCenterList.constEnd()) - throw MYMONEYEXCEPTION("Unknown costcenter '" + id + '\''); - - return *it; -} diff --git a/kmymoney/mymoney/storage/mymoneydatabasemgr.h b/kmymoney/mymoney/storage/mymoneydatabasemgr.h deleted file mode 100644 index 0091140cc..000000000 --- a/kmymoney/mymoney/storage/mymoneydatabasemgr.h +++ /dev/null @@ -1,1095 +0,0 @@ -/*************************************************************************** - mymoneydatabasemgr.h - description - ------------------- - begin : June 5 2007 - copyright : (C) 2007 by Fernando Vilas - email : Fernando Vilas - 2017 by Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef MYMONEYDATABASEMGR_H -#define MYMONEYDATABASEMGR_H - -// ---------------------------------------------------------------------------- -// QT Includes - -// ---------------------------------------------------------------------------- -// Project Includes - -#include "imymoneyserialize.h" -#include "imymoneystorage.h" -#include "mymoneykeyvaluecontainer.h" - -class MyMoneyStorageSql; - -/** - * The MyMoneyDatabaseMgr class represents the storage engine for databases. - * The actual connection and internal storage is handled through the - * MyMoneyStorageSql interface. - * - * The MyMoneyDatabaseMgr must have a MyMoneyStorageSql connected to a - * database to be useful. Once connected, data will be loaded from/sent to the - * database synchronously. The method dirty() will always return false. Making - * this many trips to the database is not very fast, so when possible, the - * data cache in MyMoneyFile is used. - * - */ -class MyMoneyDatabaseMgrPrivate; -class MyMoneyDatabaseMgr : public IMyMoneyStorage, public IMyMoneySerialize, - public MyMoneyKeyValueContainer -{ - Q_DISABLE_COPY(MyMoneyDatabaseMgr) - KMM_MYMONEY_UNIT_TESTABLE - -public: - MyMoneyDatabaseMgr(); - ~MyMoneyDatabaseMgr(); - - // general get functions - MyMoneyPayee user() const override; - QDate creationDate() const override; - QDate lastModificationDate() const override; - uint currentFixVersion() const override; - uint fileFixVersion() const override; - - // general set functions - void setUser(const MyMoneyPayee& user) override; - void setFileFixVersion(uint v) override; - - // methods provided by MyMoneyKeyValueContainer - void setValue(const QString& key, const QString& value) override; - QString value(const QString& key) const override; - void deletePair(const QString& key) override; - - /** - * This method is used to duplicate an IMyMoneyStorage object and return - * a pointer to the newly created copy. The caller of this method is the - * new owner of the object and must destroy it. - */ -// MyMoneyDatabaseMgr const * duplicate() override; - - /** - * This method is used to create a new account - * - * An exception will be thrown upon error conditions. - * - * @param account MyMoneyAccount filled with data - */ - void addAccount(MyMoneyAccount& account) override; - - /** - * This method is used to add one account as sub-ordinate to another - * (parent) account. The objects that are passed will be modified - * accordingly. - * - * An exception will be thrown upon error conditions. - * - * @param parent parent account the account should be added to - * @param account the account to be added - */ - void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) override; - - /** - * This method is used to create a new payee - * - * An exception will be thrown upon error conditions - * - * @param payee MyMoneyPayee reference to payee information - */ - void addPayee(MyMoneyPayee& payee) override; - - /** - * This method is used to retrieve information about a payee - * An exception will be thrown upon error conditions. - * - * @param id QString reference to id of payee - * - * @return MyMoneyPayee object of payee - */ - MyMoneyPayee payee(const QString& id) const override; - - /** - * This method is used to retrieve the id to a corresponding - * name of a payee/receiver. - * An exception will be thrown upon error conditions. - * - * @param payee QString reference to name of payee - * - * @return MyMoneyPayee object of payee - */ - MyMoneyPayee payeeByName(const QString& payee) const override; - - /** - * This method is used to modify an existing payee - * - * An exception will be thrown upon error conditions - * - * @param payee MyMoneyPayee reference to payee information - */ - void modifyPayee(const MyMoneyPayee& payee) override; - - /** - * This method is used to remove an existing payee - * - * An exception will be thrown upon error conditions - * - * @param payee MyMoneyPayee reference to payee information - */ - void removePayee(const MyMoneyPayee& payee) override; - - /** - * This method returns a list of the payees - * inside a MyMoneyStorage object - * - * @return QList containing the payee information - */ - QList payeeList() const override; - - /** - * This method is used to create a new tag - * - * An exception will be thrown upon error conditions - * - * @param tag MyMoneyTag reference to tag information - */ - void addTag(MyMoneyTag& tag) override; - - /** - * This method is used to retrieve information about a tag - * An exception will be thrown upon error conditions. - * - * @param id QString reference to id of tag - * - * @return MyMoneyTag object of tag - */ - MyMoneyTag tag(const QString& id) const override; - - /** - * This method is used to retrieve the id to a corresponding - * name of a tag. - * An exception will be thrown upon error conditions. - * - * @param tag QString reference to name of tag - * - * @return MyMoneyTag object of tag - */ - MyMoneyTag tagByName(const QString& tag) const override; - - /** - * This method is used to modify an existing tag - * - * An exception will be thrown upon error conditions - * - * @param tag MyMoneyTag reference to tag information - */ - void modifyTag(const MyMoneyTag& tag) override; - - /** - * This method is used to remove an existing tag - * - * An exception will be thrown upon error conditions - * - * @param tag MyMoneyTag reference to tag information - */ - void removeTag(const MyMoneyTag& tag) override; - - /** - * This method returns a list of the tags - * inside a MyMoneyStorage object - * - * @return QList containing the tag information - */ - QList tagList() const override; - - /** @todo implement all onlineJob related functions @{ */ - void modifyOnlineJob(const onlineJob& job) override; - void addOnlineJob(onlineJob& job) override; - onlineJob getOnlineJob(const QString &jobId) const override; - QList onlineJobList() const override; - void removeOnlineJob(const onlineJob&) override; - /** @} */ - - /** - * Returns the account addressed by it's id. - * - * An exception will be thrown upon error conditions. - * - * @param id id of the account to locate. - * @return reference to MyMoneyAccount object. An exception is thrown - * if the id is unknown - */ - MyMoneyAccount account(const QString& id) const override; - - /** - * This method is used to check whether a given - * account id references one of the standard accounts or not. - * - * An exception will be thrown upon error conditions. - * - * @param id account id - * @return true if account-id is one of the standards, false otherwise - */ - bool isStandardAccount(const QString& id) const override; - - /** - * This method is used to set the name for the specified standard account - * within the storage area. An exception will be thrown, if an error - * occurs - * - * @param id QString reference to one of the standard accounts. - * @param name QString reference to the name to be set - * - */ - void setAccountName(const QString& id, const QString& name) override; - - /** - * Adds an institution to the storage. A - * respective institution-ID will be generated within this record. - * The ID is stored as QString in the object passed as argument. - * - * An exception will be thrown upon error conditions. - * - * @param institution The complete institution information in a - * MyMoneyInstitution object - */ - void addInstitution(MyMoneyInstitution& institution) override; - - /** - * Adds a transaction to the file-global transaction pool. A respective - * transaction-ID will be generated within this record. The ID is stored - * QString with the object. - * - * An exception will be thrown upon error conditions. - * - * @param transaction reference to the transaction - * @param skipAccountUpdate if set, the transaction lists of the accounts - * referenced in the splits are not updated. This is used for - * bulk loading a lot of transactions but not during normal operation - */ - void addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate = false) override; - - /** - * This method is used to determince, if the account with the - * given ID is referenced by any split in m_transactionList. - * - * An exception will be thrown upon error conditions. - * - * @param id id of the account to be checked for - * @return true if account is referenced, false otherwise - */ - bool hasActiveSplits(const QString& id) const override; - - /** - * This method is used to return the actual balance of an account - * without it's sub-ordinate accounts. If a @p date is presented, - * the balance at the beginning of this date (not including any - * transaction on this date) is returned. Otherwise all recorded - * transactions are included in the balance. - * - * @param id id of the account in question - * @param date return balance for specific date - * @return balance of the account as MyMoneyMoney object - */ - MyMoneyMoney balance(const QString& id, const QDate& date) const override; - - /** - * This method is used to return the actual balance of an account - * including it's sub-ordinate accounts. If a @p date is presented, - * the balance at the beginning of this date (not including any - * transaction on this date) is returned. Otherwise all recorded - * transactions are included in the balance. - * - * @param id id of the account in question - * @param date return balance for specific date - * @return balance of the account as MyMoneyMoney object - */ - MyMoneyMoney totalBalance(const QString& id, const QDate& date) const override; - - /** - * Returns the institution of a given ID - * - * @param id id of the institution to locate - * @return MyMoneyInstitution object filled with data. If the institution - * could not be found, an exception will be thrown - */ - MyMoneyInstitution institution(const QString& id) const override; - - /** - * This method returns an indicator if the storage object has been - * changed after it has last been saved to permanent storage. - * - * @return true if changed, false if not (for a database, always false). - */ - bool dirty() const override; - - /** - * This method can be used by an external object to force the - * storage object to be dirty. This is used e.g. when an upload - * to an external destination failed but the previous storage - * to a local disk was ok. - * - * Since the database is synchronized with the application, this method - * is a no-op. - */ - void setDirty() override; - - /** - * This method returns the number of accounts currently known to this storage - * in the range 0..MAXUINT - * - * @return number of accounts currently known inside a MyMoneyFile object - */ - uint accountCount() const override; - - /** - * This method returns a list of the institutions - * inside a MyMoneyStorage object - * - * @return QList containing the - * institution information - */ - QList institutionList() const override; - - /** - * Modifies an already existing account in the file global account pool. - * - * An exception will be thrown upon error conditions. - * - * @param account reference to the new account information - * @param skipCheck allows to skip the builtin consistency checks - */ - void modifyAccount(const MyMoneyAccount& account, bool skipCheck = false) override; - - /** - * Modifies an already existing institution in the file global - * institution pool. - * - * An exception will be thrown upon error conditions. - * - * @param institution The complete new institution information - */ - void modifyInstitution(const MyMoneyInstitution& institution) override; - - /** - * This method is used to update a specific transaction in the - * transaction pool of the MyMoneyFile object - * - * An exception will be thrown upon error conditions. - * - * @param transaction reference to transaction to be changed - */ - void modifyTransaction(const MyMoneyTransaction& transaction) override; - - /** - * This method re-parents an existing account - * - * An exception will be thrown upon error conditions. - * - * @param account MyMoneyAccount reference to account to be re-parented - * @param parent MyMoneyAccount reference to new parent account - */ - void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent) override; - - /** - * This method is used to remove a transaction from the transaction - * pool (journal). - * - * An exception will be thrown upon error conditions. - * - * @param transaction const reference to transaction to be deleted - */ - void removeTransaction(const MyMoneyTransaction& transaction) override; - - /** - * This method returns the number of transactions currently known to file - * in the range 0..MAXUINT - * - * @param account QString reference to account id. If account is empty - + all transactions (the journal) will be counted. If account - * is not empty it returns the number of transactions - * that have splits in this account. - * - * @return number of transactions in journal/account - */ - uint transactionCount(const QString& account) const override; - - /** - * This method returns a QMap filled with the number of transactions - * per account. The account id serves as index into the map. If one - * needs to have all transactionCounts() for many accounts, this method - * is faster than calling transactionCount(const QString& account) many - * times. - * - * @return QMap with numbers of transactions per account - */ - QMap transactionCountMap() const override; - - /** - * This method is used to pull a list of transactions from the file - * global transaction pool. It returns all those transactions - * that match the filter passed as argument. If the filter is empty, - * the whole journal will be returned. - * The list returned is sorted according to the transactions posting date. - * If more than one transaction exists for the same date, the order among - * them is undefined. - * - * @param filter MyMoneyTransactionFilter object with the match criteria - * - * @return set of transactions in form of a QList - */ - QList transactionList(MyMoneyTransactionFilter& filter) const override; - - /** - * This method is the same as above, but instead of a return value, a - * parameter is used. - * - * @param list The set of transactions returned. The list passed in will - * be cleared before filling with results. - * @param filter MyMoneyTransactionFilter object with the match criteria - */ - void transactionList(QList& list, MyMoneyTransactionFilter& filter) const override; - - /** - * This method is the same as above, but the list contains pairs of - * transactions and splits. - * - * @param list The set of transactions returned. The list passed in will - * be cleared before filling with results. - * @param filter MyMoneyTransactionFilter object with the match criteria - */ - void transactionList(QList >& list, MyMoneyTransactionFilter& filter) const override; - - /** - * Deletes an existing account from the file global account pool - * This method only allows to remove accounts that are not - * referenced by any split. Use moveSplits() to move splits - * to another account. An exception is thrown in case of a - * problem. - * - * @param account reference to the account to be deleted. - */ - void removeAccount(const MyMoneyAccount& account) override; - - /** - * Deletes an existing institution from the file global institution pool - * Also modifies the accounts that reference this institution as - * their institution. - * - * An exception will be thrown upon error conditions. - * - * @param institution institution to be deleted. - */ - void removeInstitution(const MyMoneyInstitution& institution) override; - - /** - * This method is used to extract a transaction from the file global - * transaction pool through an id. In case of an invalid id, an - * exception will be thrown. - * - * @param id id of transaction as QString. - * @return the requested transaction - */ - MyMoneyTransaction transaction(const QString& id) const override; - - /** - * This method is used to extract a transaction from the file global - * transaction pool through an index into an account. - * - * @param account id of the account as QString - * @param idx number of transaction in this account - * @return MyMoneyTransaction object - */ - MyMoneyTransaction transaction(const QString& account, const int idx) const override; - - /** - * This method returns the number of institutions currently known to file - * in the range 0..MAXUINT - * - * @return number of institutions known to file - */ - uint institutionCount() const override; - - /** - * This method returns a list of accounts inside the storage object. - * - * @param list reference to QList receiving the account objects - * - * @note The standard accounts will not be returned - */ - void accountList(QList& list) const override; - - /** - * This method is used to return the standard liability account - * @return MyMoneyAccount liability account(group) - */ - MyMoneyAccount liability() const override; - - /** - * This method is used to return the standard asset account - * @return MyMoneyAccount asset account(group) - */ - MyMoneyAccount asset() const override; - - /** - * This method is used to return the standard expense account - * @return MyMoneyAccount expense account(group) - */ - MyMoneyAccount expense() const override; - - /** - * This method is used to return the standard income account - * @return MyMoneyAccount income account(group) - */ - MyMoneyAccount income() const override; - - /** - * This method is used to return the standard equity account - * @return MyMoneyAccount equity account(group) - */ - MyMoneyAccount equity() const override; - - /** - * This method is used to create a new security object. The ID will be - * created automatically. The object passed with the parameter @p security - * is modified to contain the assigned id. - * - * An exception will be thrown upon error conditions. - * - * @param security MyMoneySecurity filled with data - */ - void addSecurity(MyMoneySecurity& security) override; - - /** - * This method is used to modify an existing MyMoneySecurity - * object. - * - * An exception will be thrown upon erroneous situations. - * - * @param security reference to the MyMoneySecurity object to be updated - */ - void modifySecurity(const MyMoneySecurity& security) override; - - /** - * This method is used to remove an existing MyMoneySecurity object - * from the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @param security reference to the MyMoneySecurity object to be removed - */ - void removeSecurity(const MyMoneySecurity& security) override; - - /** - * This method is used to retrieve a single MyMoneySecurity object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneySecurity object - * @return MyMoneySecurity object - */ - MyMoneySecurity security(const QString& id) const override; - - /** - * This method returns a list of the security objects - * inside a MyMoneyStorage object - * - * @return QList containing objects - */ - QList securityList() const override; - - void addPrice(const MyMoneyPrice& price) override; - void removePrice(const MyMoneyPrice& price) override; - MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& _date, bool exactDate) const override; - - /** - * This method returns a list of all prices. - * - * @return MyMoneyPriceList of all MyMoneyPrice objects. - */ - MyMoneyPriceList priceList() const override; - - /** - * This method is used to add a scheduled transaction to the engine. - * It must be sure, that the id of the object is not filled. When the - * method returns to the caller, the id will be filled with the - * newly created object id value. - * - * An exception will be thrown upon erroneous situations. - * - * @param sched reference to the MyMoneySchedule object - */ - void addSchedule(MyMoneySchedule& sched) override; - - /** - * This method is used to modify an existing MyMoneySchedule - * object. Therefor, the id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param sched const reference to the MyMoneySchedule object to be updated - */ - void modifySchedule(const MyMoneySchedule& sched) override; - - /** - * This method is used to remove an existing MyMoneySchedule object - * from the engine. The id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param sched const reference to the MyMoneySchedule object to be updated - */ - void removeSchedule(const MyMoneySchedule& sched) override; - - /** - * This method is used to retrieve a single MyMoneySchedule object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneySchedule object - * @return MyMoneySchedule object - */ - MyMoneySchedule schedule(const QString& id) const override; - - /** - * This method is used to extract a list of scheduled transactions - * according to the filter criteria passed as arguments. - * - * @param accountId only search for scheduled transactions that reference - * accound @p accountId. If accountId is the empty string, - * this filter is off. Default is @p QString(). - * @param type only schedules of type @p type are searched for. - * See eMyMoney::Schedule::Type for details. - * Default is eMyMoney::Schedule::Type::Any - * @param occurrence only schedules of occurrence type @p occurrence are searched for. - * See eMyMoney::Schedule::Occurrence for details. - * Default is eMyMoney::Schedule::Occurrence::Any - * @param paymentType only schedules of payment method @p paymentType - * are searched for. - * See eMyMoney::Schedule::PaymentType for details. - * Default is eMyMoney::Schedule::PaymentType::Any - * @param startDate only schedules with payment dates after @p startDate - * are searched for. Default is all dates (QDate()). - * @param endDate only schedules with payment dates ending prior to @p endDate - * are searched for. Default is all dates (QDate()). - * @param overdue if true, only those schedules that are overdue are - * searched for. Default is false (all schedules will be returned). - * - * @return QList list of schedule objects. - */ - QList scheduleList(const QString& accountId, - eMyMoney::Schedule::Type type, - eMyMoney::Schedule::Occurrence occurrence, - eMyMoney::Schedule::PaymentType paymentType, - const QDate& startDate, - const QDate& endDate, - bool overdue) const override; - - QList scheduleListEx(int scheduleTypes, - int scheduleOcurrences, - int schedulePaymentTypes, - QDate startDate, - const QStringList& accounts) const override; - - /** - * This method is used to add a new currency object to the engine. - * The ID of the object is the trading symbol, so there is no need for an additional - * ID since the symbol is guaranteed to be unique. - * - * An exception will be thrown upon erroneous situations. - * - * @param currency reference to the MyMoneySecurity object - */ - void addCurrency(const MyMoneySecurity& currency) override; - - /** - * This method is used to modify an existing MyMoneySecurity - * object. - * - * An exception will be thrown upon erroneous situations. - * - * @param currency reference to the MyMoneyCurrency object - */ - void modifyCurrency(const MyMoneySecurity& currency) override; - - /** - * This method is used to remove an existing MyMoneySecurity object - * from the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @param currency reference to the MyMoneySecurity object - */ - void removeCurrency(const MyMoneySecurity& currency) override; - - /** - * This method is used to retrieve a single MyMoneySecurity object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneySecurity object - * @return MyMoneyCurrency object - */ - MyMoneySecurity currency(const QString& id) const override; - - /** - * This method is used to retrieve the list of all currencies - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneySecurity objects representing a currency. - */ - QList currencyList() const override; - - /** - * This method is used to retrieve the list of all reports - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneyReport objects. - */ - QList reportList() const override; - - /** - * This method is used to add a new report to the engine. - * It must be sure, that the id of the object is not filled. When the - * method returns to the caller, the id will be filled with the - * newly created object id value. - * - * An exception will be thrown upon erroneous situations. - * - * @param report reference to the MyMoneyReport object - */ - void addReport(MyMoneyReport& report) override; - - /** - * This method is used to modify an existing MyMoneyReport - * object. Therefor, the id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param report const reference to the MyMoneyReport object to be updated - */ - void modifyReport(const MyMoneyReport& report) override; - - /** - * This method returns the number of reports currently known to file - * in the range 0..MAXUINT - * - * @return number of reports known to file - */ - uint countReports() const override; - - /** - * This method is used to retrieve a single MyMoneyReport object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneyReport object - * @return MyMoneyReport object - */ - MyMoneyReport report(const QString& id) const override; - - /** - * This method is used to remove an existing MyMoneyReport object - * from the engine. The id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param report const reference to the MyMoneyReport object to be updated - */ - void removeReport(const MyMoneyReport& report) override; - - /** - * This method is used to retrieve the list of all budgets - * known to the engine. - * - * An exception will be thrown upon erroneous situations. - * - * @return QList of all MyMoneyBudget objects. - */ - QList budgetList() const override; - - /** - * This method is used to add a new budget to the engine. - * It must be sure, that the id of the object is not filled. When the - * method returns to the caller, the id will be filled with the - * newly created object id value. - * - * An exception will be thrown upon erroneous situations. - * - * @param budget reference to the MyMoneyBudget object - */ - void addBudget(MyMoneyBudget& budget) override; - - /** - * This method is used to retrieve the id to a corresponding - * name of a budget - * An exception will be thrown upon error conditions. - * - * @param budget QString reference to name of budget - * - * @return MyMoneyBudget object of budget - */ - MyMoneyBudget budgetByName(const QString& budget) const override; - - /** - * This method is used to modify an existing MyMoneyBudget - * object. Therefor, the id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param budget const reference to the MyMoneyBudget object to be updated - */ - void modifyBudget(const MyMoneyBudget& budget) override; - - /** - * This method returns the number of budgets currently known to file - * in the range 0..MAXUINT - * - * @return number of budgets known to file - */ - uint countBudgets() const override; - - /** - * This method is used to retrieve a single MyMoneyBudget object. - * The id of the object must be supplied in the parameter @p id. - * - * An exception will be thrown upon erroneous situations. - * - * @param id QString containing the id of the MyMoneyBudget object - * @return MyMoneyBudget object - */ - MyMoneyBudget budget(const QString& id) const override; - - /** - * This method is used to remove an existing MyMoneyBudget object - * from the engine. The id attribute of the object must be set. - * - * An exception will be thrown upon erroneous situations. - * - * @param budget const reference to the MyMoneyBudget object to be updated - */ - void removeBudget(const MyMoneyBudget& budget) override; - - /** - * This method returns a list of all cost center objects - */ - QList costCenterList() const override; - - /** - * @brief Return cost center object by id - */ - MyMoneyCostCenter costCenter(const QString& id) const override; - - /** - * Clear all internal caches (used internally for performance measurements) - */ - void clearCache(); - - /** - * This method checks, if the given @p object is referenced - * by another engine object. - * - * @param obj const reference to object to be checked - * @param skipCheck QBitArray with eStorage::Reference bits set for which - * the check should be skipped - * - * @retval false @p object is not referenced - * @retval true @p institution is referenced - */ - bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const override; - - /** - * This method is provided to allow closing of the database before logoff - */ - void close() override; - - /** - * These methods have to be provided to allow transaction safe data handling. - */ - void startTransaction() override; - bool commitTransaction() override; - void rollbackTransaction() override; - - // general set functions - void setCreationDate(const QDate& val) override; - - /** - * This method is used to get a SQL reader for subsequent database access - */ - QExplicitlySharedDataPointer connectToDatabase - (const QUrl &url) override; - /** - * This method is used when a database file is open, and the data is to - * be saved in a different file or format. It will ensure that all data - * from the database is available in memory to enable it to be written. - */ - void fillStorage() override; - - /** - * This method is used to set the last modification date of - * the storage object. It also clears the dirty flag and should - * therefor be called as last operation when loading from a - * file. - * - * @param val QDate of last modification - */ - void setLastModificationDate(const QDate& val) override; - - /** - * This method returns whether a given transaction is already in memory, to avoid - * reloading it from the database - */ - bool isDuplicateTransaction(const QString&) const override; - - void loadAccounts(const QMap& map) override; - void loadTransactions(const QMap& map) override; - void loadInstitutions(const QMap& map) override; - void loadPayees(const QMap& map) override; - void loadTags(const QMap& map) override; - void loadSchedules(const QMap& map) override; - void loadSecurities(const QMap& map) override; - void loadCurrencies(const QMap& map) override; - void loadReports(const QMap& reports) override; - void loadBudgets(const QMap& budgets) override; - void loadPrices(const MyMoneyPriceList& list) override; - void loadOnlineJobs(const QMap& onlineJobs) override; - void loadCostCenters(const QMap& costCenters) override; - - //void loadPayeeIdentifier(const QMap& idents); - - ulong accountId() const override; - ulong transactionId() const override; - ulong payeeId() const override; - ulong tagId() const override; - ulong institutionId() const override; - ulong scheduleId() const override; - ulong securityId() const override; - ulong reportId() const override; - ulong budgetId() const override; - ulong onlineJobId() const override; - ulong payeeIdentifierId() const; - ulong costCenterId() const override; - - void loadAccountId(ulong id) override; - void loadTransactionId(ulong id) override; - void loadPayeeId(ulong id) override; - void loadTagId(ulong id) override; - void loadInstitutionId(ulong id) override; - void loadScheduleId(ulong id) override; - void loadSecurityId(ulong id) override; - void loadReportId(ulong id) override; - void loadBudgetId(ulong id) override; - void loadOnlineJobId(ulong id) override; - void loadPayeeIdentifierId(ulong id); - void loadCostCenterId(ulong id) override; - - /** - * This method is used to retrieve the whole set of key/value pairs - * from the container. It is meant to be used for permanent storage - * functionality. See MyMoneyKeyValueContainer::pairs() for details. - * - * @return QMap containing all key/value pairs of - * this container. - */ - QMap pairs() const override; - - /** - * This method is used to initially store a set of key/value pairs - * in the container. It is meant to be used for loading functionality - * from permanent storage. See MyMoneyKeyValueContainer::setPairs() - * for details - * - * @param list const QMap containing the set of - * key/value pairs to be loaded into the container. - * - * @note All existing key/value pairs in the container will be deleted. - */ - void setPairs(const QMap& list) override; - - /** - * This method recalculates the balances of all accounts - * based on the transactions stored in the engine. - */ - void rebuildAccountBalances() override; - -private: - MyMoneyDatabaseMgrPrivate* const d_ptr; - Q_DECLARE_PRIVATE_D(MyMoneyDatabaseMgr::d_ptr, MyMoneyDatabaseMgr) - - /** - * This method is used to get the next valid ID for a institution - * @return id for a institution - */ - QString nextInstitutionID() override; - - /** - * This method is used to get the next valid ID for an account - * @return id for an account - */ - QString nextAccountID() override; - - /** - * This method is used to get the next valid ID for a transaction - * @return id for a transaction - */ - QString nextTransactionID() override; - - /** - * This method is used to get the next valid ID for a payee - * @return id for a payee - */ - QString nextPayeeID() override; - - /** - * This method is used to get the next valid ID for a tag - * @return id for a tag - */ - QString nextTagID() override; - - /** - * This method is used to get the next valid ID for a scheduled transaction - * @return id for a scheduled transaction - */ - QString nextScheduleID() override; - - /** - * This method is used to get the next valid ID for an security object. - * @return id for an security object - */ - QString nextSecurityID() override; - - QString nextReportID() override; - - /** @brief get next valid id for an onlineJob */ - QString nextOnlineJobID() override; - - /** @brief get next valid id for payeeIdentifier */ - QString nextPayeeIdentifierID(); - - /** @brief get next valid id for a cost center */ - QString nextCostCenterID() override; - - /** - * This method is used to get the next valid ID for a budget object. - * @return id for an budget object - */ - QString nextBudgetID() override; - -}; -#endif diff --git a/kmymoney/mymoney/storage/mymoneydatabasemgr_p.h b/kmymoney/mymoney/storage/mymoneydatabasemgr_p.h deleted file mode 100644 index 60f507996..000000000 --- a/kmymoney/mymoney/storage/mymoneydatabasemgr_p.h +++ /dev/null @@ -1,141 +0,0 @@ -/*************************************************************************** - mymoneydatabasemgr.cpp - ------------------- - begin : June 5 2007 - copyright : (C) 2007 by Fernando Vilas - email : Fernando Vilas - 2017 by Łukasz Wojniłowicz - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef MYMONEYDATABASEMGR_P_H -#define MYMONEYDATABASEMGR_P_H - -#include "mymoneydatabasemgr.h" - -#include -#include - -// ---------------------------------------------------------------------------- -// QT Includes - -#include -#include - -// ---------------------------------------------------------------------------- -// KDE Includes - -#include - -// ---------------------------------------------------------------------------- -// Project Includes - -#include "mymoneytransactionfilter.h" -#include "mymoneycategory.h" -#include "mymoneyfile.h" -#include "mymoneyinstitution.h" -#include "mymoneyaccount.h" -#include "mymoneysecurity.h" -#include "mymoneytag.h" -#include "mymoneybudget.h" -#include "mymoneyschedule.h" -#include "mymoneymoney.h" -#include "mymoneysplit.h" -#include "mymoneypayee.h" -#include "mymoneyreport.h" -#include "mymoneycostcenter.h" -#include "mymoneymap.h" -#include "mymoneystoragesql.h" -#include "mymoneyenums.h" -#include "storageenums.h" - -using namespace eStorage; - -class MyMoneyDatabaseMgrPrivate -{ - Q_DISABLE_COPY(MyMoneyDatabaseMgrPrivate) - Q_DECLARE_PUBLIC(MyMoneyDatabaseMgr) - -public: - explicit MyMoneyDatabaseMgrPrivate(MyMoneyDatabaseMgr* qq) : - q_ptr(qq), - m_creationDate(QDate::currentDate()), - m_currentFixVersion(0), - m_fileFixVersion(0), - m_lastModificationDate(QDate::currentDate()), - m_sql(0) - { - } - - ~MyMoneyDatabaseMgrPrivate() - { - } - - void removeReferences(const QString& id) - { - QMap::const_iterator it_r; - QMap::const_iterator it_b; - - // remove from reports - QMap reportList = m_sql->fetchReports(); - for (it_r = reportList.constBegin(); it_r != reportList.constEnd(); ++it_r) { - MyMoneyReport r = *it_r; - r.removeReference(id); - // reportList.modify(r.id(), r); - } - - // remove from budgets - QMap budgetList = m_sql->fetchBudgets(); - for (it_b = budgetList.constBegin(); it_b != budgetList.constEnd(); ++it_b) { - MyMoneyBudget b = *it_b; - b.removeReference(id); - // budgetList.modify(b.id(), b); - } - } - - MyMoneyDatabaseMgr *q_ptr; - /** - * This member variable keeps the creation date of this MyMoneyDatabaseMgr - * object. It is set during the constructor and can only be modified using - * the stream read operator. - */ - QDate m_creationDate; - - /** - * This member variable contains the current fix level of application - * data files. (see kmymoneyview.cpp) - */ - uint m_currentFixVersion; - - /** - * This member variable contains the current fix level of the - * presently open data file. (see kmymoneyview.cpp) - */ - uint m_fileFixVersion; - - /** - * This member variable keeps the date of the last modification of - * the MyMoneyDatabaseMgr object. - */ - QDate m_lastModificationDate; - - /** - * This contains the interface with SQL reader for database access - */ - QExplicitlySharedDataPointer m_sql; - - /** - * This member variable keeps the User information. - * @see setUser() - */ - MyMoneyPayee m_user; -}; -#endif diff --git a/kmymoney/mymoney/storage/mymoneydbdef.cpp b/kmymoney/mymoney/storage/mymoneydbdef.cpp index 66e2bd1f2..d63bd66fc 100644 --- a/kmymoney/mymoney/storage/mymoneydbdef.cpp +++ b/kmymoney/mymoney/storage/mymoneydbdef.cpp @@ -1,776 +1,776 @@ /*************************************************************************** mymoneydbdef.h ------------------- begin : 20 February 2010 copyright : (C) 2010 by Fernando Vilas email : tonybloom@users.sourceforge.net : Fernando Vilas ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneydbdef.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneydbdriver.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneyexception.h" -#include "imymoneystorage.h" +#include "mymoneystoragemgr.h" #include //***************** THE CURRENT VERSION OF THE DATABASE LAYOUT **************** unsigned int MyMoneyDbDef::m_currentVersion = 12; // ************************* Build table descriptions **************************** MyMoneyDbDef::MyMoneyDbDef() { FileInfo(); PluginInfo(); Institutions(); Payees(); PayeesPayeeIdentifier(); Tags(); TagSplits(); // a table to bind tags and splits Accounts(); AccountsPayeeIdentifier(); Transactions(); Splits(); KeyValuePairs(); Schedules(); SchedulePaymentHistory(); Securities(); Prices(); Currencies(); Reports(); Budgets(); Balances(); OnlineJobs(); PayeeIdentifier(); CostCenter(); } /* PRIMARYKEY - these fields combine to form a unique key field on which the db will create an index NOTNULL - this field should never be null UNSIGNED - for numeric types, indicates the field is UNSIGNED ?ISKEY - where there is no primary key, these fields can be used to uniquely identify a record Default is that a field is not a part of a primary key, nullable, and if numeric, signed */ static const bool PRIMARYKEY = true; static const bool NOTNULL = true; static const bool UNSIGNED = false; #define appendField(a) fields.append(QExplicitlySharedDataPointer(new a)) void MyMoneyDbDef::FileInfo() { QList< QExplicitlySharedDataPointer > fields; appendField(MyMoneyDbColumn("version", "varchar(16)")); appendField(MyMoneyDbColumn("created", "date")); appendField(MyMoneyDbColumn("lastModified", "date")); appendField(MyMoneyDbColumn("baseCurrency", "char(3)")); appendField(MyMoneyDbIntColumn("institutions", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("accounts", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("payees", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("tags", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 7)); appendField(MyMoneyDbIntColumn("transactions", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("splits", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("securities", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("prices", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("currencies", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("schedules", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("reports", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("kvps", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbColumn("dateRangeStart", "date")); appendField(MyMoneyDbColumn("dateRangeEnd", "date")); appendField(MyMoneyDbIntColumn("hiInstitutionId", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("hiPayeeId", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("hiTagId", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 7)); appendField(MyMoneyDbIntColumn("hiAccountId", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("hiTransactionId", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("hiScheduleId", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("hiSecurityId", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbIntColumn("hiReportId", MyMoneyDbIntColumn::BIG, UNSIGNED)); appendField(MyMoneyDbColumn("encryptData", "varchar(255)")); appendField(MyMoneyDbColumn("updateInProgress", "char(1)")); appendField(MyMoneyDbIntColumn("budgets", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 1)); appendField(MyMoneyDbIntColumn("hiBudgetId", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 1)); appendField(MyMoneyDbIntColumn("hiOnlineJobId", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 8)); appendField(MyMoneyDbIntColumn("hiPayeeIdentifierId", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 8)); appendField(MyMoneyDbColumn("logonUser", "varchar(255)", false, false, 1)); appendField(MyMoneyDbDatetimeColumn("logonAt", false, false, 1)); appendField(MyMoneyDbIntColumn("fixLevel", MyMoneyDbIntColumn::MEDIUM, UNSIGNED, false, false, 6)); MyMoneyDbTable t("kmmFileInfo", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Institutions() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL)); appendField(MyMoneyDbTextColumn("manager")); appendField(MyMoneyDbTextColumn("routingCode")); appendField(MyMoneyDbTextColumn("addressStreet")); appendField(MyMoneyDbTextColumn("addressCity")); appendField(MyMoneyDbTextColumn("addressZipcode")); appendField(MyMoneyDbTextColumn("telephone")); MyMoneyDbTable t("kmmInstitutions", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Payees() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbTextColumn("name")); appendField(MyMoneyDbTextColumn("reference")); appendField(MyMoneyDbTextColumn("email")); appendField(MyMoneyDbTextColumn("addressStreet")); appendField(MyMoneyDbTextColumn("addressCity")); appendField(MyMoneyDbTextColumn("addressZipcode")); appendField(MyMoneyDbTextColumn("addressState")); appendField(MyMoneyDbTextColumn("telephone")); appendField(MyMoneyDbTextColumn("notes", MyMoneyDbTextColumn::LONG, false, false, 5)); appendField(MyMoneyDbColumn("defaultAccountId", "varchar(32)", false, false, 5)); appendField(MyMoneyDbIntColumn("matchData", MyMoneyDbIntColumn::TINY, UNSIGNED, false, false, 5)); appendField(MyMoneyDbColumn("matchIgnoreCase", "char(1)", false, false, 5)); appendField(MyMoneyDbTextColumn("matchKeys", MyMoneyDbTextColumn::MEDIUM, false, false, 5)); MyMoneyDbTable t("kmmPayees", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::PayeesPayeeIdentifier() { QList > fields; appendField(MyMoneyDbColumn("payeeId", "varchar(32)", PRIMARYKEY, NOTNULL, 8)); appendField(MyMoneyDbIntColumn("\"order\"", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL, 8, 9)); appendField(MyMoneyDbIntColumn("userOrder", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL, 10)); appendField(MyMoneyDbColumn("identifierId", "varchar(32)", false, NOTNULL, 8)); MyMoneyDbTable t("kmmPayeesPayeeIdentifier", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Tags() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbTextColumn("name")); appendField(MyMoneyDbColumn("closed", "char(1)", false, false, 5)); appendField(MyMoneyDbTextColumn("notes", MyMoneyDbTextColumn::LONG, false, false, 5)); appendField(MyMoneyDbTextColumn("tagColor")); MyMoneyDbTable t("kmmTags", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::TagSplits() { QList > fields; appendField(MyMoneyDbColumn("transactionId", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("tagId", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbIntColumn("splitId", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL)); MyMoneyDbTable t("kmmTagSplits", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Accounts() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("institutionId", "varchar(32)")); appendField(MyMoneyDbColumn("parentId", "varchar(32)")); appendField(MyMoneyDbDatetimeColumn("lastReconciled")); appendField(MyMoneyDbDatetimeColumn("lastModified")); appendField(MyMoneyDbColumn("openingDate", "date")); appendField(MyMoneyDbTextColumn("accountNumber")); appendField(MyMoneyDbColumn("accountType", "varchar(16)", false, NOTNULL)); appendField(MyMoneyDbTextColumn("accountTypeString")); appendField(MyMoneyDbColumn("isStockAccount", "char(1)")); appendField(MyMoneyDbTextColumn("accountName")); appendField(MyMoneyDbTextColumn("description")); appendField(MyMoneyDbColumn("currencyId", "varchar(32)")); appendField(MyMoneyDbTextColumn("balance")); appendField(MyMoneyDbTextColumn("balanceFormatted")); appendField(MyMoneyDbIntColumn("transactionCount", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 1)); MyMoneyDbTable t("kmmAccounts", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::AccountsPayeeIdentifier() { QList > fields; appendField(MyMoneyDbColumn("accountId", "varchar(32)", PRIMARYKEY, NOTNULL, 8)); appendField(MyMoneyDbIntColumn("\"order\"", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL, 8, 9)); appendField(MyMoneyDbIntColumn("userOrder", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL, 10)); appendField(MyMoneyDbColumn("identifierId", "varchar(32)", false, NOTNULL, 8)); MyMoneyDbTable t("kmmAccountsPayeeIdentifier", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Transactions() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("txType", "char(1)")); appendField(MyMoneyDbDatetimeColumn("postDate")); appendField(MyMoneyDbTextColumn("memo")); appendField(MyMoneyDbDatetimeColumn("entryDate")); appendField(MyMoneyDbColumn("currencyId", "char(3)")); appendField(MyMoneyDbTextColumn("bankId")); MyMoneyDbTable t("kmmTransactions", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Splits() { QList > fields; appendField(MyMoneyDbColumn("transactionId", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("txType", "char(1)")); appendField(MyMoneyDbIntColumn("splitId", MyMoneyDbIntColumn::SMALL, UNSIGNED, PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("payeeId", "varchar(32)")); appendField(MyMoneyDbDatetimeColumn("reconcileDate")); appendField(MyMoneyDbColumn("action", "varchar(16)")); appendField(MyMoneyDbColumn("reconcileFlag", "char(1)")); appendField(MyMoneyDbTextColumn("value", MyMoneyDbTextColumn::NORMAL, false, NOTNULL)); appendField(MyMoneyDbColumn("valueFormatted", "text")); appendField(MyMoneyDbTextColumn("shares", MyMoneyDbTextColumn::NORMAL, false, NOTNULL)); appendField(MyMoneyDbTextColumn("sharesFormatted")); appendField(MyMoneyDbTextColumn("price", MyMoneyDbTextColumn::NORMAL, false, false, 2)); appendField(MyMoneyDbTextColumn("priceFormatted", MyMoneyDbTextColumn::MEDIUM, false, false, 2)); appendField(MyMoneyDbTextColumn("memo")); appendField(MyMoneyDbColumn("accountId", "varchar(32)", false, NOTNULL)); appendField(MyMoneyDbColumn("costCenterId", "varchar(32)", false, false, 9)); appendField(MyMoneyDbColumn("checkNumber", "varchar(32)")); appendField(MyMoneyDbDatetimeColumn("postDate", false, false, 1)); appendField(MyMoneyDbTextColumn("bankId", MyMoneyDbTextColumn::MEDIUM, false, false, 5)); MyMoneyDbTable t("kmmSplits", fields); QStringList list; list << "accountId" << "txType"; t.addIndex("kmmSplitsaccount_type", list, false); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::KeyValuePairs() { QList > fields; appendField(MyMoneyDbColumn("kvpType", "varchar(16)", false, NOTNULL)); appendField(MyMoneyDbColumn("kvpId", "varchar(32)")); appendField(MyMoneyDbColumn("kvpKey", "varchar(255)", false, NOTNULL)); appendField(MyMoneyDbTextColumn("kvpData")); MyMoneyDbTable t("kmmKeyValuePairs", fields); QStringList list; list << "kvpType" << "kvpId"; t.addIndex("type_id", list, false); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Schedules() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL)); appendField(MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::TINY, UNSIGNED, false, NOTNULL)); appendField(MyMoneyDbTextColumn("typeString")); appendField(MyMoneyDbIntColumn("occurence", MyMoneyDbIntColumn::SMALL, UNSIGNED, false, // krazy:exclude=spelling NOTNULL)); appendField(MyMoneyDbIntColumn("occurenceMultiplier", MyMoneyDbIntColumn::SMALL, UNSIGNED, // krazy:exclude=spelling false, NOTNULL, 3)); appendField(MyMoneyDbTextColumn("occurenceString")); // krazy:exclude=spelling appendField(MyMoneyDbIntColumn("paymentType", MyMoneyDbIntColumn::TINY, UNSIGNED)); appendField(MyMoneyDbTextColumn("paymentTypeString", MyMoneyDbTextColumn::LONG)); appendField(MyMoneyDbColumn("startDate", "date", false, NOTNULL)); appendField(MyMoneyDbColumn("endDate", "date")); appendField(MyMoneyDbColumn("fixed", "char(1)", false, NOTNULL)); appendField(MyMoneyDbColumn("lastDayInMonth", "char(1)", false, NOTNULL, 11, std::numeric_limits::max(), QLatin1String("N"))); appendField(MyMoneyDbColumn("autoEnter", "char(1)", false, NOTNULL)); appendField(MyMoneyDbColumn("lastPayment", "date")); appendField(MyMoneyDbColumn("nextPaymentDue", "date")); appendField(MyMoneyDbIntColumn("weekendOption", MyMoneyDbIntColumn::TINY, UNSIGNED, false, NOTNULL)); appendField(MyMoneyDbTextColumn("weekendOptionString")); MyMoneyDbTable t("kmmSchedules", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::SchedulePaymentHistory() { QList > fields; appendField(MyMoneyDbColumn("schedId", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("payDate", "date", PRIMARYKEY, NOTNULL)); MyMoneyDbTable t("kmmSchedulePaymentHistory", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Securities() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("name", "text", false, NOTNULL)); appendField(MyMoneyDbTextColumn("symbol")); appendField(MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::SMALL, UNSIGNED, false, NOTNULL)); appendField(MyMoneyDbTextColumn("typeString")); appendField(MyMoneyDbColumn("smallestAccountFraction", "varchar(24)")); appendField(MyMoneyDbIntColumn("pricePrecision", MyMoneyDbIntColumn::SMALL, UNSIGNED, false, NOTNULL)); appendField(MyMoneyDbTextColumn("tradingMarket")); appendField(MyMoneyDbColumn("tradingCurrency", "char(3)")); appendField(MyMoneyDbIntColumn("roundingMethod", MyMoneyDbIntColumn::SMALL, UNSIGNED, false, NOTNULL, 11, std::numeric_limits::max(), QString("%1").arg(AlkValue::RoundRound))); MyMoneyDbTable t("kmmSecurities", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Prices() { QList > fields; appendField(MyMoneyDbColumn("fromId", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("toId", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("priceDate", "date", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbTextColumn("price", MyMoneyDbTextColumn::NORMAL, false, NOTNULL)); appendField(MyMoneyDbTextColumn("priceFormatted")); appendField(MyMoneyDbTextColumn("priceSource")); MyMoneyDbTable t("kmmPrices", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Currencies() { QList > fields; appendField(MyMoneyDbColumn("ISOcode", "char(3)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbTextColumn("name", MyMoneyDbTextColumn::NORMAL, false, NOTNULL)); appendField(MyMoneyDbIntColumn("type", MyMoneyDbIntColumn::SMALL, UNSIGNED)); appendField(MyMoneyDbTextColumn("typeString")); appendField(MyMoneyDbIntColumn("symbol1", MyMoneyDbIntColumn::SMALL, UNSIGNED)); appendField(MyMoneyDbIntColumn("symbol2", MyMoneyDbIntColumn::SMALL, UNSIGNED)); appendField(MyMoneyDbIntColumn("symbol3", MyMoneyDbIntColumn::SMALL, UNSIGNED)); appendField(MyMoneyDbColumn("symbolString", "varchar(255)")); appendField(MyMoneyDbColumn("smallestCashFraction", "varchar(24)")); appendField(MyMoneyDbColumn("smallestAccountFraction", "varchar(24)")); // the default for price precision was taken from MyMoneySecurity appendField(MyMoneyDbIntColumn("pricePrecision", MyMoneyDbIntColumn::SMALL, UNSIGNED, false, NOTNULL, 11, std::numeric_limits::max(), QLatin1String("4"))); MyMoneyDbTable t("kmmCurrencies", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Reports() { QList > fields; appendField(MyMoneyDbColumn("name", "varchar(255)", false, NOTNULL)); appendField(MyMoneyDbTextColumn("XML", MyMoneyDbTextColumn::LONG)); appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL, 6)); MyMoneyDbTable t("kmmReportConfig", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::OnlineJobs() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL, 8)); appendField(MyMoneyDbColumn("type", "varchar(255)", false, NOTNULL, 8)); appendField(MyMoneyDbDatetimeColumn("jobSend", false, false, 8)); appendField(MyMoneyDbDatetimeColumn("bankAnswerDate", false, false, 8)); appendField(MyMoneyDbColumn("state", "varchar(15)", false, NOTNULL, 8)); appendField(MyMoneyDbColumn("locked", "char(1)", false, NOTNULL, 8)); MyMoneyDbTable t("kmmOnlineJobs", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::PayeeIdentifier() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL, 8)); appendField(MyMoneyDbColumn("type", "varchar(255)", false, false, 8)); MyMoneyDbTable t("kmmPayeeIdentifier", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::PluginInfo() { QList > fields; appendField(MyMoneyDbColumn("iid", "varchar(255)", PRIMARYKEY, NOTNULL, 8)); appendField(MyMoneyDbIntColumn("versionMajor", MyMoneyDbIntColumn::TINY, false, false, NOTNULL, 8)); appendField(MyMoneyDbIntColumn("versionMinor", MyMoneyDbIntColumn::TINY, false, false, false, 8)); appendField(MyMoneyDbTextColumn("uninstallQuery", MyMoneyDbTextColumn::LONG, false, false, 8)); MyMoneyDbTable t("kmmPluginInfo", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Budgets() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("name", "text", false, NOTNULL)); appendField(MyMoneyDbColumn("start", "date", false, NOTNULL)); appendField(MyMoneyDbTextColumn("XML", MyMoneyDbTextColumn::LONG)); MyMoneyDbTable t("kmmBudgetConfig", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::CostCenter() { QList > fields; appendField(MyMoneyDbColumn("id", "varchar(32)", PRIMARYKEY, NOTNULL)); appendField(MyMoneyDbColumn("name", "text", false, NOTNULL)); MyMoneyDbTable t("kmmCostCenter", fields); t.buildSQLStrings(); m_tables[t.name()] = t; } void MyMoneyDbDef::Balances() { MyMoneyDbView v("kmmBalances", "CREATE VIEW kmmBalances AS " "SELECT kmmAccounts.id AS id, kmmAccounts.currencyId, " "kmmSplits.txType, kmmSplits.value, kmmSplits.shares, " "kmmSplits.postDate AS balDate, " "kmmTransactions.currencyId AS txCurrencyId " "FROM kmmAccounts, kmmSplits, kmmTransactions " "WHERE kmmSplits.txType = 'N' " "AND kmmSplits.accountId = kmmAccounts.id " "AND kmmSplits.transactionId = kmmTransactions.id;"); m_views[v.name()] = v; } // function to write create SQL to a stream const QString MyMoneyDbDef::generateSQL(const QExplicitlySharedDataPointer& driver) const { QString retval; // Add the CREATE TABLE strings table_iterator tt = tableBegin(); while (tt != tableEnd()) { retval += (*tt).generateCreateSQL(driver) + '\n'; ++tt; } // Add the CREATE OR REPLACE VIEW strings view_iterator vt = viewBegin(); while (vt != viewEnd()) { retval += (*vt).createString() + '\n'; ++vt; } retval += '\n'; // Add the strings to populate kmmFileInfo with initial values MyMoneyDbTable fi = m_tables["kmmFileInfo"]; QString qs = fi.insertString(); MyMoneyDbTable::field_iterator fit; for (fit = fi.begin(); fit != fi.end(); ++fit) { QString toReplace = (*fit)->name(); toReplace.prepend(':'); QString replace = "NULL"; if ((*fit)->name() == "version") replace = QString::number(m_currentVersion); if ((*fit)->name() == "fixLevel") replace = QString::number (MyMoneyFile::instance()->storage()->currentFixVersion()); if ((*fit)->name() == "created") replace = QLatin1Char('\'') + QDate::currentDate().toString(Qt::ISODate) + QLatin1Char('\''); if ((*fit)->name() == "lastModified") replace = QLatin1Char('\'') + QDate::currentDate().toString(Qt::ISODate) + QLatin1Char('\''); if ((*fit)->name() == "updateInProgress") replace = enclose("N"); qs.replace(QRegExp(toReplace + "(?=[,\\s\\)])"), replace); // only replace parameters followed by comma, whitespace, closing parenthesis - otherwise // conflicts may occur if one parameter starts with the name of another one. } qs += "\n\n"; retval += qs; // Add the strings to create the initial accounts qs.clear(); QList stdList; stdList.append(MyMoneyFile::instance()->asset()); stdList.append(MyMoneyFile::instance()->equity()); stdList.append(MyMoneyFile::instance()->expense()); stdList.append(MyMoneyFile::instance()->income()); stdList.append(MyMoneyFile::instance()->liability()); for (int i = 0; i < stdList.count(); ++i) { MyMoneyAccount* pac = &stdList[i]; MyMoneyDbTable ac = m_tables["kmmAccounts"]; qs = ac.insertString(); MyMoneyDbTable::field_iterator act; // do the following in reverse so the 'formatted' fields are // correctly handled. // Hmm, how does one use a QValueListIterator in reverse // It'll be okay in Qt4 with QListIterator for (act = ac.end(), --act; act != ac.begin(); --act) { QString toReplace = (*act)->name(); toReplace.prepend(':'); QString replace = "NULL"; if ((*act)->name() == "accountType") replace = QString::number((int)pac->accountType()); if ((*act)->name() == "accountTypeString") replace = enclose(pac->name()); if ((*act)->name() == "isStockAccount") replace = enclose("N"); if ((*act)->name() == "accountName") replace = enclose(pac->name()); qs.replace(toReplace, replace); } qs.replace(":id", enclose(pac->id())); // a real kludge qs += "\n\n"; retval += qs; } return retval; } //***************************************************************************** void MyMoneyDbTable::addIndex(const QString& name, const QStringList& columns, bool unique) { m_indices.push_back(MyMoneyDbIndex(m_name, name, columns, unique)); } void MyMoneyDbTable::buildSQLStrings() { // build fixed SQL strings for this table // build the insert string with placeholders for each field QString qs = QString("INSERT INTO %1 (").arg(name()); QString ws = ") VALUES ("; field_iterator ft = m_fields.constBegin(); while (ft != m_fields.constEnd()) { qs += QString("%1, ").arg((*ft)->name()); ws += QString(":%1, ").arg((*ft)->name()); ++ft; } qs = qs.left(qs.length() - 2); ws = ws.left(ws.length() - 2); m_insertString = qs + ws + ");"; // build a 'select all' string (select * is deprecated) // don't terminate with semicolon coz we may want a where or order clause m_selectAllString = "SELECT " + columnList() + " FROM " + name(); // build an update string; key fields go in the where clause qs = "UPDATE " + name() + " SET "; ws.clear(); ft = m_fields.constBegin(); while (ft != m_fields.constEnd()) { if ((*ft)->isPrimaryKey()) { if (!ws.isEmpty()) ws += " AND "; ws += QString("%1 = :%2").arg((*ft)->name()).arg((*ft)->name()); } else { qs += QString("%1 = :%2, ").arg((*ft)->name()).arg((*ft)->name()); } ++ft; } qs = qs.left(qs.length() - 2); if (!ws.isEmpty()) qs += " WHERE " + ws; m_updateString = qs + ';'; // build a delete string; where clause as for update qs = "DELETE FROM " + name(); if (!ws.isEmpty()) qs += " WHERE " + ws; m_deleteString = qs + ';'; // Setup the column name hash ft = m_fields.constBegin(); m_fieldOrder.reserve(m_fields.size()); int i = 0; while (ft != m_fields.constEnd()) { m_fieldOrder[(*ft)->name()] = i; ++i; ++ft; } } const QString MyMoneyDbTable::columnList(const int version) const { field_iterator ft = m_fields.begin(); QString qs; ft = m_fields.begin(); while (ft != m_fields.end()) { if ((*ft)->initVersion() <= version && (*ft)->lastVersion() >= version) { qs += QString("%1, ").arg((*ft)->name()); } ++ft; } return (qs.left(qs.length() - 2)); } const QString MyMoneyDbTable::generateCreateSQL(const QExplicitlySharedDataPointer& driver, int version) const { QString qs = QString("CREATE TABLE %1 (").arg(name()); QString pkey; for (field_iterator it = m_fields.begin(); it != m_fields.end(); ++it) { if ((*it)->initVersion() <= version && (*it)->lastVersion() >= version) { qs += (*it)->generateDDL(driver) + ", "; if ((*it)->isPrimaryKey()) pkey += (*it)->name() + ", "; } } if (!pkey.isEmpty()) { qs += "PRIMARY KEY (" + pkey; qs = qs.left(qs.length() - 2) + "))"; } else { qs = qs.left(qs.length() - 2) + ')'; } qs += driver->tableOptionString(); qs += ";\n"; for (index_iterator ii = m_indices.begin(); ii != m_indices.end(); ++ii) { qs += (*ii).generateDDL(driver); } return qs; } const QString MyMoneyDbTable::dropPrimaryKeyString(const QExplicitlySharedDataPointer& driver) const { return driver->dropPrimaryKeyString(m_name); } bool MyMoneyDbTable::hasPrimaryKey(int version) const { field_iterator ft = m_fields.constBegin(); while (ft != m_fields.constEnd()) { if ((*ft)->initVersion() <= version && (*ft)->lastVersion() >= version) { if ((*ft)->isPrimaryKey()) return (true); } ++ft; } return (false); } const QString MyMoneyDbTable::modifyColumnString(const QExplicitlySharedDataPointer& driver, const QString& columnName, const MyMoneyDbColumn& newDef) const { return driver->modifyColumnString(m_name, columnName, newDef); } int MyMoneyDbTable::fieldNumber(const QString& name) const { QHash::ConstIterator i = m_fieldOrder.find(name); if (m_fieldOrder.constEnd() == i) { throw MYMONEYEXCEPTION(QString("Unknown field %1 in table %2").arg(name).arg(m_name)); } return i.value(); } //***************************************************************************** const QString MyMoneyDbIndex::generateDDL(const QExplicitlySharedDataPointer& driver) const { Q_UNUSED(driver); QString qs = "CREATE "; if (m_unique) qs += "UNIQUE "; qs += "INDEX " + m_table + '_' + m_name + "_idx ON " + m_table + " ("; // The following should probably be revised. MySQL supports an index on // partial columns, but not on a function. Postgres supports an index on // the result of an SQL function, but not a partial column. There should be // a way to merge these, and support other DBMSs like SQLite at the same time. // For now, if we just use plain columns, this will work fine. for (QStringList::ConstIterator it = m_columns.constBegin(); it != m_columns.constEnd(); ++it) { qs += *it + ','; } qs = qs.left(qs.length() - 1) + ");\n"; return qs; } //***************************************************************************** // These are the actual column types. // MyMoneyDbColumn* MyMoneyDbColumn::clone() const { return (new MyMoneyDbColumn(*this)); } MyMoneyDbIntColumn* MyMoneyDbIntColumn::clone() const { return (new MyMoneyDbIntColumn(*this)); } MyMoneyDbDatetimeColumn* MyMoneyDbDatetimeColumn::clone() const { return (new MyMoneyDbDatetimeColumn(*this)); } MyMoneyDbTextColumn* MyMoneyDbTextColumn::clone() const { return (new MyMoneyDbTextColumn(*this)); } const QString MyMoneyDbColumn::generateDDL(const QExplicitlySharedDataPointer& driver) const { Q_UNUSED(driver); QString qs = name() + ' ' + type(); if (isNotNull()) qs += " NOT NULL"; if (!defaultValue().isEmpty()) qs += QString(" DEFAULT \"%1\"").arg(defaultValue()); return qs; } const QString MyMoneyDbIntColumn::generateDDL(const QExplicitlySharedDataPointer& driver) const { QString qs = driver->intString(*this); if (!defaultValue().isEmpty()) qs += QString(" DEFAULT %1").arg(defaultValue()); return qs; } const QString MyMoneyDbTextColumn::generateDDL(const QExplicitlySharedDataPointer& driver) const { return driver->textString(*this); } const QString MyMoneyDbDatetimeColumn::generateDDL(const QExplicitlySharedDataPointer& driver) const { return driver->timestampString(*this); } diff --git a/kmymoney/mymoney/storage/mymoneystorageanon.cpp b/kmymoney/mymoney/storage/mymoneystorageanon.cpp index cf1d8b856..d9891b31f 100644 --- a/kmymoney/mymoney/storage/mymoneystorageanon.cpp +++ b/kmymoney/mymoney/storage/mymoneystorageanon.cpp @@ -1,328 +1,328 @@ /*************************************************************************** mymoneystorageanon.cpp ------------------- begin : Thu Oct 24 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneystorageanon.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes -#include "imymoneyserialize.h" +#include "mymoneystoragemgr.h" #include "mymoneyreport.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneybudget.h" #include "mymoneytransaction.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneyexception.h" QStringList MyMoneyStorageANON::zKvpNoModify = QString("kmm-baseCurrency,OpeningBalanceAccount,PreferredAccount,Tax,fixed-interest,interest-calculation,payee,schedule,term,kmm-online-source,kmm-brokerage-account,lastStatementDate,kmm-sort-reconcile,kmm-sort-std,kmm-iconpos,mm-closed,payee,schedule,term,lastImportedTransactionDate,VatAccount,VatRate,kmm-matched-tx,Imported,priceMode").split(','); QStringList MyMoneyStorageANON::zKvpXNumber = QString("final-payment,loan-amount,periodic-payment,lastStatementBalance").split(','); MyMoneyStorageANON::MyMoneyStorageANON() : MyMoneyStorageXML() { // Choose a quasi-random 0.0-100.0 factor which will be applied to all splits this time // around. int msec; do { msec = QTime::currentTime().msec(); } while (msec == 0); m_factor = MyMoneyMoney(msec, 10).reduce(); } MyMoneyStorageANON::~MyMoneyStorageANON() { } -void MyMoneyStorageANON::readFile(QIODevice* , IMyMoneySerialize*) +void MyMoneyStorageANON::readFile(QIODevice* , MyMoneyStorageMgr*) { throw MYMONEYEXCEPTION("Cannot read a file through MyMoneyStorageANON!!"); } void MyMoneyStorageANON::writeUserInformation(QDomElement& userInfo) { MyMoneyPayee user = m_storage->user(); userInfo.setAttribute(QString("name"), hideString(user.name())); userInfo.setAttribute(QString("email"), hideString(user.email())); QDomElement address = m_doc->createElement("ADDRESS"); address.setAttribute(QString("street"), hideString(user.address())); address.setAttribute(QString("city"), hideString(user.city())); address.setAttribute(QString("county"), hideString(user.state())); address.setAttribute(QString("zipcode"), hideString(user.postcode())); address.setAttribute(QString("telephone"), hideString(user.telephone())); userInfo.appendChild(address); } void MyMoneyStorageANON::writeInstitution(QDomElement& institution, const MyMoneyInstitution& _i) { MyMoneyInstitution i(_i); // mangle fields i.setName(i.id()); i.setManager(hideString(i.manager())); i.setSortcode(hideString(i.sortcode())); i.setStreet(hideString(i.street())); i.setCity(hideString(i.city())); i.setPostcode(hideString(i.postcode())); i.setTelephone(hideString(i.telephone())); MyMoneyStorageXML::writeInstitution(institution, i); } void MyMoneyStorageANON::writePayee(QDomElement& payee, const MyMoneyPayee& _p) { MyMoneyPayee p(_p); p.setName(p.id()); p.setReference(hideString(p.reference())); p.setAddress(hideString(p.address())); p.setCity(hideString(p.city())); p.setPostcode(hideString(p.postcode())); p.setState(hideString(p.state())); p.setTelephone(hideString(p.telephone())); p.setNotes(hideString(p.notes())); bool ignoreCase; QStringList keys; MyMoneyPayee::payeeMatchType matchType = p.matchData(ignoreCase, keys); QRegExp exp("[A-Za-z]"); p.setMatchData(matchType, ignoreCase, keys.join(";").replace(exp, "x").split(';')); // Data from plugins cannot be estranged, yet. p.resetPayeeIdentifiers(); MyMoneyStorageXML::writePayee(payee, p); } void MyMoneyStorageANON::writeTag(QDomElement& tag, const MyMoneyTag& _ta) { MyMoneyTag ta(_ta); ta.setName(ta.id()); ta.setNotes(hideString(ta.notes())); MyMoneyStorageXML::writeTag(tag, ta); } void MyMoneyStorageANON::writeAccount(QDomElement& account, const MyMoneyAccount& _p) { MyMoneyAccount p(_p); p.setNumber(hideString(p.number())); p.setName(p.id()); p.setDescription(hideString(p.description())); fakeKeyValuePair(p); // Remove the online banking settings entirely. p.setOnlineBankingSettings(MyMoneyKeyValueContainer()); MyMoneyStorageXML::writeAccount(account, p); } void MyMoneyStorageANON::fakeTransaction(MyMoneyTransaction& tx) { MyMoneyTransaction tn = tx; // hide transaction data tn.setMemo(tx.id()); tn.setBankID(hideString(tx.bankID())); // hide split data foreach (const auto split, tx.splits()) { MyMoneySplit s = split; s.setMemo(QString("%1/%2").arg(tn.id()).arg(s.id())); if (s.value() != MyMoneyMoney::autoCalc) { s.setValue((s.value() * m_factor)); s.setShares((s.shares() * m_factor)); } s.setNumber(hideString(s.number())); // obfuscate a possibly matched transaction as well if (s.isMatched()) { MyMoneyTransaction t = s.matchedTransaction(); fakeTransaction(t); s.removeMatch(); s.addMatch(t); } tn.modifySplit(s); } tx = tn; fakeKeyValuePair(tx); } void MyMoneyStorageANON::fakeKeyValuePair(MyMoneyKeyValueContainer& kvp) { QMap pairs; QMap::const_iterator it; for (it = kvp.pairs().constBegin(); it != kvp.pairs().constEnd(); ++it) { if (zKvpXNumber.contains(it.key()) || it.key().left(3) == "ir-") pairs[it.key()] = hideNumber(MyMoneyMoney(it.value())).toString(); else if (zKvpNoModify.contains(it.key())) pairs[it.key()] = it.value(); else pairs[it.key()] = hideString(it.value()); } kvp.setPairs(pairs); } void MyMoneyStorageANON::writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx) { MyMoneyTransaction tn = tx; fakeTransaction(tn); MyMoneyStorageXML::writeTransaction(transactions, tn); } void MyMoneyStorageANON::writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& sx) { MyMoneySchedule sn = sx; MyMoneyTransaction tn = sn.transaction(); fakeTransaction(tn); sn.setName(sx.id()); sn.setTransaction(tn, true); MyMoneyStorageXML::writeSchedule(scheduledTx, sn); } void MyMoneyStorageANON::writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security) { MyMoneySecurity s = security; s.setName(security.id()); fakeKeyValuePair(s); MyMoneyStorageXML::writeSecurity(securityElement, s); } QString MyMoneyStorageANON::hideString(const QString& _in) const { return QString(_in).fill('x'); } MyMoneyMoney MyMoneyStorageANON::hideNumber(const MyMoneyMoney& _in) const { MyMoneyMoney result; static MyMoneyMoney counter = MyMoneyMoney(100, 100); // preserve sign if (_in.isNegative()) result = MyMoneyMoney::MINUS_ONE; else result = MyMoneyMoney::ONE; result = result * counter; counter += MyMoneyMoney("10/100"); // preserve > 1000 if (_in >= MyMoneyMoney(1000, 1)) result = result * MyMoneyMoney(1000, 1); if (_in <= MyMoneyMoney(-1000, 1)) result = result * MyMoneyMoney(1000, 1); return result.convert(); } void MyMoneyStorageANON::fakeBudget(MyMoneyBudget& bx) { MyMoneyBudget bn; bn.setName(bx.id()); bn.setBudgetStart(bx.budgetStart()); bn = MyMoneyBudget(bx.id(), bn); QList list = bx.getaccounts(); QList::iterator it; for (it = list.begin(); it != list.end(); ++it) { // only add the account if there is a budget entered if (!(*it).balance().isZero()) { MyMoneyBudget::AccountGroup account; account.setId((*it).id()); account.setBudgetLevel((*it).budgetLevel()); account.setBudgetSubaccounts((*it).budgetSubaccounts()); QMap plist = (*it).getPeriods(); QMap::const_iterator it_p; for (it_p = plist.constBegin(); it_p != plist.constEnd(); ++it_p) { MyMoneyBudget::PeriodGroup pGroup; pGroup.setAmount((*it_p).amount() * m_factor); pGroup.setStartDate((*it_p).startDate()); account.addPeriod(pGroup.startDate(), pGroup); } bn.setAccount(account, account.id()); } } bx = bn; } void MyMoneyStorageANON::writeBudget(QDomElement& budgets, const MyMoneyBudget& b) { MyMoneyBudget bn = b; fakeBudget(bn); MyMoneyStorageXML::writeBudget(budgets, bn); } void MyMoneyStorageANON::writeReport(QDomElement& reports, const MyMoneyReport& r) { MyMoneyReport rn = r; rn.setName(rn.id()); rn.setComment(hideString(rn.comment())); MyMoneyStorageXML::writeReport(reports, rn); } void MyMoneyStorageANON::writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job) { Q_UNUSED(onlineJobs); Q_UNUSED(job); } diff --git a/kmymoney/mymoney/storage/mymoneystorageanon.h b/kmymoney/mymoney/storage/mymoneystorageanon.h index 282388978..266490a51 100644 --- a/kmymoney/mymoney/storage/mymoneystorageanon.h +++ b/kmymoney/mymoney/storage/mymoneystorageanon.h @@ -1,129 +1,118 @@ /*************************************************************************** mymoneystorageanon.h ------------------- begin : Thu Oct 24 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Ace Jone ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGEANON_H #define MYMONEYSTORAGEANON_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystoragexml.h" #include "mymoneymoney.h" -//class QDomElement; - -//class MyMoneyAccount; -//class MyMoneyInstitution; -//class MyMoneySecurity; -//class MyMoneyBudget; -//class MyMoneySchedule; -//class MyMoneyPayee; -//class MyMoneyTag; -//class MyMoneyTransaction; class MyMoneyKeyValueContainer; -//class onlineJob; /** * @author Kevin Tambascio (ktambascio@users.sourceforge.net) */ #define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info #define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects /** * This class provides storage of an anonymized version of the current * file. Any object with an ID (account, transaction, etc) is renamed * with that ID. Any other string value the user typed in is replaced with * x's equal in length to the original string. Any numeric value is * replaced with an arbitrary number which matches the sign of the original. * * The purpose of this class is to give users a way to send a developer * their file without comprimising their financial data. If a user * encounters an error, they should try saving the anonymous version of the * file and see if the error is still there. If so, they should notify the * list of the problem, and then when requested, send the anonymous file * privately to the developer who takes the problem. I still don't think * it's wise to post the file to the public list...maybe I'm just paranoid. * * @author Ace Jones */ class MyMoneyStorageANON : public MyMoneyStorageXML { public: MyMoneyStorageANON(); virtual ~MyMoneyStorageANON(); protected: void writeUserInformation(QDomElement& userInfo); void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i); void writePayee(QDomElement& payees, const MyMoneyPayee& p); void writeTag(QDomElement& tags, const MyMoneyTag& ta); void writeAccount(QDomElement& accounts, const MyMoneyAccount& p); void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx); void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx); void writeBudget(QDomElement& budgets, const MyMoneyBudget& b); void writeReport(QDomElement& reports, const MyMoneyReport& r); - void readFile(QIODevice* s, IMyMoneySerialize* storage); + void readFile(QIODevice* s, MyMoneyStorageMgr* storage); void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security); /** Cannot remove prive data from plugins, yet. It is simply doing nothing. */ void writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job); QDomElement findChildElement(const QString& name, const QDomElement& root); private: /** * The list of key-value pairs to not modify */ static QStringList zKvpNoModify; /** * The list of key-value pairs which are numbers to be hidden */ static QStringList zKvpXNumber; QString hideString(const QString&) const; MyMoneyMoney hideNumber(const MyMoneyMoney&) const; void fakeTransaction(MyMoneyTransaction& tn); void fakeBudget(MyMoneyBudget& bn); void fakeKeyValuePair(MyMoneyKeyValueContainer& _kvp); MyMoneyMoney m_factor; }; #endif diff --git a/kmymoney/mymoney/storage/mymoneystoragedump.cpp b/kmymoney/mymoney/storage/mymoneystoragedump.cpp index 7ad4d196f..69d6e0a1f 100644 --- a/kmymoney/mymoney/storage/mymoneystoragedump.cpp +++ b/kmymoney/mymoney/storage/mymoneystoragedump.cpp @@ -1,499 +1,499 @@ /*************************************************************************** mymoneystoragedump.cpp - description ------------------- begin : Sun May 5 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneystoragedump.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes -#include "imymoneyserialize.h" -#include "imymoneystorage.h" +#include "mymoneystoragemgr.h" +#include "mymoneystoragemgr_p.h" #include "mymoneyexception.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneytag.h" #include "mymoneyreport.h" #include "mymoneybudget.h" #include "mymoneyschedule.h" #include "mymoneypayee.h" #include "mymoneymoney.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyenums.h" MyMoneyStorageDump::MyMoneyStorageDump() { } MyMoneyStorageDump::~MyMoneyStorageDump() { } -void MyMoneyStorageDump::readStream(QDataStream& /* s */, IMyMoneySerialize* /* storage */) +void MyMoneyStorageDump::readStream(QDataStream& /* s */, MyMoneyStorageMgr* /* storage */) { qDebug("Reading not supported by MyMoneyStorageDump!!"); } -void MyMoneyStorageDump::writeStream(QDataStream& _s, IMyMoneySerialize* _storage) +void MyMoneyStorageDump::writeStream(QDataStream& _s, MyMoneyStorageMgr* _storage) { QTextStream s(_s.device()); - IMyMoneyStorage* storage = dynamic_cast(_storage); + MyMoneyStorageMgr* storage = _storage; MyMoneyPayee user = storage->user(); s << "File-Info\n"; s << "---------\n"; s << "user name = " << user.name() << "\n"; s << "user street = " << user.address() << "\n"; s << "user city = " << user.city() << "\n"; s << "user city = " << user.state() << "\n"; s << "user zip = " << user.postcode() << "\n"; s << "user telephone = " << user.telephone() << "\n"; s << "user e-mail = " << user.email() << "\n"; s << "creation date = " << storage->creationDate().toString(Qt::ISODate) << "\n"; s << "last modification date = " << storage->lastModificationDate().toString(Qt::ISODate) << "\n"; s << "base currency = " << storage->value("kmm-baseCurrency") << "\n"; s << "\n"; s << "Internal-Info\n"; s << "-------------\n"; QList list_a; storage->accountList(list_a); - s << "accounts = " << list_a.count() << ", next id = " << _storage->accountId() << "\n"; + s << "accounts = " << list_a.count() << ", next id = " << _storage->d_func()->m_nextAccountID << "\n"; MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList list_t; storage->transactionList(list_t, filter); QList::ConstIterator it_t; - s << "transactions = " << list_t.count() << ", next id = " << _storage->transactionId() << "\n"; + s << "transactions = " << list_t.count() << ", next id = " << _storage->d_func()->m_nextTransactionID << "\n"; QMap xferCount; foreach (const auto transaction, list_t) { auto accountCount = 0; foreach (const auto split, transaction.splits()) { auto acc = storage->account(split.accountId()); if (acc.accountGroup() != eMyMoney::Account::Type::Expense && acc.accountGroup() != eMyMoney::Account::Type::Income) accountCount++; } if (accountCount > 1) xferCount[accountCount] = xferCount[accountCount] + 1; } QMap::ConstIterator it_cnt; for (it_cnt = xferCount.constBegin(); it_cnt != xferCount.constEnd(); ++it_cnt) { s << " " << *it_cnt << " of them references " << it_cnt.key() << " accounts\n"; } - s << "payees = " << _storage->payeeList().count() << ", next id = " << _storage->payeeId() << "\n"; - s << "tags = " << _storage->tagList().count() << ", next id = " << _storage->tagId() << "\n"; - s << "institutions = " << _storage->institutionList().count() << ", next id = " << _storage->institutionId() << "\n"; + s << "payees = " << _storage->payeeList().count() << ", next id = " << _storage->d_func()->m_nextPayeeID << "\n"; + s << "tags = " << _storage->tagList().count() << ", next id = " << _storage->d_func()->m_nextTagID << "\n"; + s << "institutions = " << _storage->institutionList().count() << ", next id = " << _storage->d_func()->m_nextInstitutionID << "\n"; s << "schedules = " << _storage->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, - QDate(), QDate(), false).count() << ", next id = " << _storage->scheduleId() << "\n"; + QDate(), QDate(), false).count() << ", next id = " << _storage->d_func()->m_nextScheduleID << "\n"; s << "\n"; s << "Institutions\n"; s << "------------\n"; QList list_i = storage->institutionList(); QList::ConstIterator it_i; for (it_i = list_i.constBegin(); it_i != list_i.constEnd(); ++it_i) { s << " ID = " << (*it_i).id() << "\n"; s << " Name = " << (*it_i).name() << "\n"; s << "\n"; } s << "\n"; s << "Payees" << "\n"; s << "------" << "\n"; QList list_p = storage->payeeList(); QList::ConstIterator it_p; for (it_p = list_p.constBegin(); it_p != list_p.constEnd(); ++it_p) { s << " ID = " << (*it_p).id() << "\n"; s << " Name = " << (*it_p).name() << "\n"; s << " Address = " << (*it_p).address() << "\n"; s << " City = " << (*it_p).city() << "\n"; s << " State = " << (*it_p).state() << "\n"; s << " Zip = " << (*it_p).postcode() << "\n"; s << " E-Mail = " << (*it_p).email() << "\n"; s << " Telephone = " << (*it_p).telephone() << "\n"; s << " Reference = " << (*it_p).reference() << "\n"; s << "\n"; } s << "\n"; s << "Tags" << "\n"; s << "------" << "\n"; QList list_ta = storage->tagList(); QList::ConstIterator it_ta; for (it_ta = list_ta.constBegin(); it_ta != list_ta.constEnd(); ++it_ta) { s << " ID = " << (*it_ta).id() << "\n"; s << " Name = " << (*it_ta).name() << "\n"; s << " Closed = " << (*it_ta).isClosed() << "\n"; s << " TagColor = " << (*it_ta).tagColor().name() << "\n"; s << " Notes = " << (*it_ta).notes() << "\n"; s << "\n"; } s << "\n"; s << "Accounts" << "\n"; s << "--------" << "\n"; list_a.push_front(storage->equity()); list_a.push_front(storage->expense()); list_a.push_front(storage->income()); list_a.push_front(storage->liability()); list_a.push_front(storage->asset()); QList::ConstIterator it_a; for (it_a = list_a.constBegin(); it_a != list_a.constEnd(); ++it_a) { s << " ID = " << (*it_a).id() << "\n"; s << " Name = " << (*it_a).name() << "\n"; s << " Number = " << (*it_a).number() << "\n"; s << " Description = " << (*it_a).description() << "\n"; s << " Type = " << (int)(*it_a).accountType() << "\n"; if ((*it_a).currencyId().isEmpty()) { s << " Currency = unknown\n"; } else { if ((*it_a).isInvest()) { s << " Equity = " << storage->security((*it_a).currencyId()).name() << "\n"; } else { s << " Currency = " << storage->currency((*it_a).currencyId()).name() << "\n"; } } s << " Parent = " << (*it_a).parentAccountId(); if (!(*it_a).parentAccountId().isEmpty()) { MyMoneyAccount parent = storage->account((*it_a).parentAccountId()); s << " (" << parent.name() << ")"; } else { s << "n/a"; } s << "\n"; s << " Institution = " << (*it_a).institutionId(); if (!(*it_a).institutionId().isEmpty()) { MyMoneyInstitution inst = storage->institution((*it_a).institutionId()); s << " (" << inst.name() << ")"; } else { s << "n/a"; } s << "\n"; s << " Opening date = " << (*it_a).openingDate().toString(Qt::ISODate) << "\n"; s << " Last modified = " << (*it_a).lastModified().toString(Qt::ISODate) << "\n"; s << " Last reconciled = " << (*it_a).lastReconciliationDate().toString(Qt::ISODate) << "\n"; s << " Balance = " << (*it_a).balance().formatMoney("", 2) << "\n"; dumpKVP(" KVP: ", s, *it_a); dumpKVP(" OnlineBankingSettings: ", s, (*it_a).onlineBankingSettings()); QStringList list_s = (*it_a).accountList(); QStringList::ConstIterator it_s; if (list_s.count() > 0) { s << " Children =" << "\n"; } for (it_s = list_s.constBegin(); it_s != list_s.constEnd(); ++it_s) { MyMoneyAccount child = storage->account(*it_s); s << " " << *it_s << " (" << child.name() << ")\n"; } s << "\n"; } s << "\n"; #if 0 s << "Currencies" << "\n"; s << "----------" << "\n"; QList list_c = storage->currencyList(); QList::ConstIterator it_c; for (it_c = list_c.begin(); it_c != list_c.end(); ++it_c) { s << " Name = " << (*it_c).name() << "\n"; s << " ID = " << (*it_c).id() << "\n"; s << " Symbol = " << (*it_c).tradingSymbol() << "\n"; s << " Parts/Unit = " << (*it_c).partsPerUnit() << "\n"; s << " smallest cash fraction = " << (*it_c).smallestCashFraction() << "\n"; s << " smallest account fraction = " << (*it_c).smallestAccountFraction() << "\n"; dumpPriceHistory(s, (*it_c).priceHistory()); s << "\n"; } s << "\n"; #endif s << "Securities" << "\n"; s << "----------" << "\n"; QList list_e = storage->securityList(); QList::ConstIterator it_e; for (it_e = list_e.constBegin(); it_e != list_e.constEnd(); ++it_e) { s << " Name = " << (*it_e).name() << "\n"; s << " ID = " << (*it_e).id() << "\n"; s << " Market = " << (*it_e).tradingMarket() << "\n"; s << " Symbol = " << (*it_e).tradingSymbol() << "\n"; s << " Currency = " << (*it_e).tradingCurrency() << " ("; if ((*it_e).tradingCurrency().isEmpty()) { s << "unknown"; } else { MyMoneySecurity tradingCurrency = storage->currency((*it_e).tradingCurrency()); if (!tradingCurrency.isCurrency()) { s << "invalid currency: "; } s << tradingCurrency.name(); } s << ")\n"; s << " Type = " << MyMoneySecurity::securityTypeToString((*it_e).securityType()) << "\n"; s << " smallest account fraction = " << (*it_e).smallestAccountFraction() << "\n"; s << " price precision = " << (*it_e).pricePrecision() << "\n"; s << " KVP: " << "\n"; QMapkvp = (*it_e).pairs(); QMap::Iterator it; for (it = kvp.begin(); it != kvp.end(); ++it) { s << " '" << it.key() << "' = '" << it.value() << "'\n"; } s << "\n"; } s << "\n"; s << "Prices" << "\n"; s << "--------" << "\n"; MyMoneyPriceList list_pr = _storage->priceList(); MyMoneyPriceList::ConstIterator it_pr; for (it_pr = list_pr.constBegin(); it_pr != list_pr.constEnd(); ++it_pr) { s << " From = " << it_pr.key().first << "\n"; s << " To = " << it_pr.key().second << "\n"; MyMoneyPriceEntries::ConstIterator it_pre; for (it_pre = (*it_pr).constBegin(); it_pre != (*it_pr).constEnd(); ++it_pre) { s << " Date = " << (*it_pre).date().toString() << "\n"; s << " Price = " << (*it_pre).rate(QString()).formatMoney("", 8) << "\n"; s << " Source = " << (*it_pre).source() << "\n"; s << " From = " << (*it_pre).from() << "\n"; s << " To = " << (*it_pre).to() << "\n"; } s << "\n"; } s << "\n"; s << "Transactions" << "\n"; s << "------------" << "\n"; for (it_t = list_t.constBegin(); it_t != list_t.constEnd(); ++it_t) { dumpTransaction(s, storage, *it_t); } s << "\n"; s << "Schedules" << "\n"; s << "---------" << "\n"; auto list_s = storage->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), false); QList::ConstIterator it_s; for (it_s = list_s.constBegin(); it_s != list_s.constEnd(); ++it_s) { s << " ID = " << (*it_s).id() << "\n"; s << " Name = " << (*it_s).name() << "\n"; s << " Startdate = " << (*it_s).startDate().toString(Qt::ISODate) << "\n"; if ((*it_s).willEnd()) s << " Enddate = " << (*it_s).endDate().toString(Qt::ISODate) << "\n"; else s << " Enddate = not specified\n"; s << " Occurence = " << (*it_s).occurrenceToString() << "\n"; // krazy:exclude=spelling s << " OccurenceMultiplier = " << (*it_s).occurrenceMultiplier() << "\n"; // krazy:exclude=spelling s << " Type = " << MyMoneySchedule::scheduleTypeToString((*it_s).type()) << "\n"; s << " Paymenttype = " << MyMoneySchedule::paymentMethodToString((*it_s).paymentType()) << "\n"; s << " Fixed = " << (*it_s).isFixed() << "\n"; s << " LastDayInDate = " << (*it_s).lastDayInMonth() << "\n"; s << " AutoEnter = " << (*it_s).autoEnter() << "\n"; if ((*it_s).lastPayment().isValid()) s << " Last payment = " << (*it_s).lastPayment().toString(Qt::ISODate) << "\n"; else s << " Last payment = not defined" << "\n"; if ((*it_s).isFinished()) s << " Next payment = payment finished" << "\n"; else { s << " Next payment = " << (*it_s).nextDueDate().toString(Qt::ISODate) << "\n"; if ((*it_s).isOverdue()) s << " = overdue!" << "\n"; } QList list_d; QList::ConstIterator it_d; list_d = (*it_s).recordedPayments(); if (list_d.count() > 0) { s << " Recorded payments" << "\n"; for (it_d = list_d.constBegin(); it_d != list_d.constEnd(); ++it_d) { s << " " << (*it_d).toString(Qt::ISODate) << "\n"; } } s << " TRANSACTION\n"; dumpTransaction(s, storage, (*it_s).transaction()); } s << "\n"; s << "Reports" << "\n"; s << "-------" << "\n"; QList list_r = storage->reportList(); QList::ConstIterator it_r; for (it_r = list_r.constBegin(); it_r != list_r.constEnd(); ++it_r) { s << " ID = " << (*it_r).id() << "\n"; s << " Name = " << (*it_r).name() << "\n"; } s << "Budgets" << "\n"; s << "-------" << "\n"; QList list_b = storage->budgetList(); QList::ConstIterator it_b; for (it_b = list_b.constBegin(); it_b != list_b.constEnd(); ++it_b) { s << " ID = " << (*it_b).id() << "\n"; s << " Name = " << (*it_b).name() << "\n"; } } void MyMoneyStorageDump::dumpKVP(const QString& headline, QTextStream& s, const MyMoneyKeyValueContainer &kvp, int indent) { QString ind; ind.fill(' ', indent); s << ind << headline << "\n"; QMap::const_iterator it; for (it = kvp.pairs().constBegin(); it != kvp.pairs().constEnd(); ++it) { s << ind << " '" << it.key() << "' = '" << it.value() << "'\n"; } } -void MyMoneyStorageDump::dumpTransaction(QTextStream& s, IMyMoneyStorage* storage, const MyMoneyTransaction& it_t) +void MyMoneyStorageDump::dumpTransaction(QTextStream& s, MyMoneyStorageMgr* storage, const MyMoneyTransaction& it_t) { s << " ID = " << it_t.id() << "\n"; s << " Postdate = " << it_t.postDate().toString(Qt::ISODate) << "\n"; s << " EntryDate = " << it_t.entryDate().toString(Qt::ISODate) << "\n"; s << " Commodity = [" << it_t.commodity() << "]\n"; s << " Memo = " << it_t.memo() << "\n"; s << " BankID = " << it_t.bankID() << "\n"; dumpKVP("KVP:", s, it_t, 2); s << " Splits\n"; s << " ------\n"; foreach (const auto split, it_t.splits()) { s << " ID = " << split.id() << "\n"; s << " Transaction = " << split.transactionId() << "\n"; s << " Payee = " << split.payeeId(); if (!split.payeeId().isEmpty()) { MyMoneyPayee p = storage->payee(split.payeeId()); s << " (" << p.name() << ")" << "\n"; } else s << " ()\n"; for (int i = 0; i < split.tagIdList().size(); i++) { s << " Tag = " << split.tagIdList()[i]; if (!split.tagIdList()[i].isEmpty()) { MyMoneyTag ta = storage->tag(split.tagIdList()[i]); s << " (" << ta.name() << ")" << "\n"; } else s << " ()\n"; } s << " Account = " << split.accountId(); MyMoneyAccount acc; try { acc = storage->account(split.accountId()); s << " (" << acc.name() << ") [" << acc.currencyId() << "]\n"; } catch (const MyMoneyException &) { s << " (---) [---]\n"; } s << " Memo = " << split.memo() << "\n"; if (split.value() == MyMoneyMoney::autoCalc) s << " Value = will be calculated" << "\n"; else s << " Value = " << split.value().formatMoney("", 2) << " (" << split.value().toString() << ")\n"; s << " Shares = " << split.shares().formatMoney("", 2) << " (" << split.shares().toString() << ")\n"; s << " Action = '" << split.action() << "'\n"; s << " Nr = '" << split.number() << "'\n"; s << " ReconcileFlag = '" << reconcileToString(split.reconcileFlag()) << "'\n"; if (split.reconcileFlag() != eMyMoney::Split::State::NotReconciled) { s << " ReconcileDate = " << split.reconcileDate().toString(Qt::ISODate) << "\n"; } s << " BankID = " << split.bankID() << "\n"; dumpKVP("KVP:", s, split, 4); s << "\n"; } s << "\n"; } #define i18n QString const QString MyMoneyStorageDump::reconcileToString(eMyMoney::Split::State flag) const { QString rc; switch (flag) { case eMyMoney::Split::State::NotReconciled: rc = i18nc("Reconciliation status 'Not Reconciled'", "not reconciled"); break; case eMyMoney::Split::State::Cleared: rc = i18nc("Reconciliation status 'Cleared'", "cleared"); break; case eMyMoney::Split::State::Reconciled: rc = i18nc("Reconciliation status 'Reconciled'", "reconciled"); break; case eMyMoney::Split::State::Frozen: rc = i18nc("Reconciliation status 'Frozen'", "frozen"); break; default: rc = i18nc("Reconciliation status unknown", "unknown"); break; } return rc; } #if 0 void MyMoneyStorageDump::dumpPriceHistory(QTextStream& s, const equity_price_history history) { if (history.count() != 0) { s << " Price History:\n"; equity_price_history::const_iterator it_price = history.begin(); while (it_price != history.end()) { s << " " << it_price.key().toString() << ": " << it_price.data().toDouble() << "\n"; it_price++; } } } #endif diff --git a/kmymoney/mymoney/storage/mymoneystoragedump.h b/kmymoney/mymoney/storage/mymoneystoragedump.h index b5efd1f45..a86ad36b4 100644 --- a/kmymoney/mymoney/storage/mymoneystoragedump.h +++ b/kmymoney/mymoney/storage/mymoneystoragedump.h @@ -1,60 +1,60 @@ /*************************************************************************** mymoneystoragedump.h - description ------------------- begin : Sun May 5 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGEDUMP_H #define MYMONEYSTORAGEDUMP_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneykeyvaluecontainer.h" /** * @author Thomas Baumgart */ -class IMyMoneySerialize; -class IMyMoneyStorage; +class MyMoneyStorageMgr; +class MyMoneyStorageMgr; class MyMoneyTransaction; class QTextStream; namespace eMyMoney { namespace Split { enum class State; } } class MyMoneyStorageDump { public: MyMoneyStorageDump(); ~MyMoneyStorageDump(); - void readStream(QDataStream& s, IMyMoneySerialize* storage); - void writeStream(QDataStream& s, IMyMoneySerialize* storage); + void readStream(QDataStream& s, MyMoneyStorageMgr* storage); + void writeStream(QDataStream& s, MyMoneyStorageMgr* storage); private: - void dumpTransaction(QTextStream& s, IMyMoneyStorage* storage, const MyMoneyTransaction& it_t); + void dumpTransaction(QTextStream& s, MyMoneyStorageMgr* storage, const MyMoneyTransaction& it_t); void dumpKVP(const QString& headline, QTextStream& s, const MyMoneyKeyValueContainer &kvp, int indent = 0); const QString reconcileToString(eMyMoney::Split::State flag) const; }; #endif diff --git a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.cpp b/kmymoney/mymoney/storage/mymoneystoragemgr.cpp similarity index 68% rename from kmymoney/mymoney/storage/mymoneyseqaccessmgr.cpp rename to kmymoney/mymoney/storage/mymoneystoragemgr.cpp index 5960b4125..05acdf0cf 100644 --- a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.cpp +++ b/kmymoney/mymoney/storage/mymoneystoragemgr.cpp @@ -1,2237 +1,1937 @@ /*************************************************************************** - mymoneyseqaccessmgr.cpp + mymoneygenericstorage.cpp ------------------- begin : Sun May 5 2002 copyright : (C) 2000-2002 by Michael Edwardes 2002 Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ -#include "mymoneyseqaccessmgr_p.h" +#include "mymoneystoragemgr_p.h" // ---------------------------------------------------------------------------- // QT Includes +#include + // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes -#include "mymoneyreport.h" #include "mymoneyprice.h" -#include "onlinejob.h" -#include "mymoneystoragenames.h" - -#define TRY try { -#define CATCH } catch (const MyMoneyException &e) { -#define PASS } catch (const MyMoneyException &e) { throw; } - -const int INSTITUTION_ID_SIZE = 6; -const int ACCOUNT_ID_SIZE = 6; -const int TRANSACTION_ID_SIZE = 18; -const int PAYEE_ID_SIZE = 6; -const int TAG_ID_SIZE = 6; -const int SCHEDULE_ID_SIZE = 6; -const int SECURITY_ID_SIZE = 6; -const int REPORT_ID_SIZE = 6; -const int BUDGET_ID_SIZE = 6; -const int ONLINE_JOB_ID_SIZE = 6; -const int COSTCENTER_ID_SIZE = 6; - -using namespace MyMoneyStandardAccounts; -#include -MyMoneySeqAccessMgr::MyMoneySeqAccessMgr() : - d_ptr(new MyMoneySeqAccessMgrPrivate(this)) -{ - Q_D(MyMoneySeqAccessMgr); - // setup standard accounts - MyMoneyAccount acc_l; - acc_l.setAccountType(eMyMoney::Account::Type::Liability); - acc_l.setName("Liability"); - MyMoneyAccount liability(stdAccNames[stdAccLiability], acc_l); - - MyMoneyAccount acc_a; - acc_a.setAccountType(eMyMoney::Account::Type::Asset); - acc_a.setName("Asset"); - MyMoneyAccount asset(stdAccNames[stdAccAsset], acc_a); - - MyMoneyAccount acc_e; - acc_e.setAccountType(eMyMoney::Account::Type::Expense); - acc_e.setName("Expense"); - MyMoneyAccount expense(stdAccNames[stdAccExpense], acc_e); - - MyMoneyAccount acc_i; - acc_i.setAccountType(eMyMoney::Account::Type::Income); - acc_i.setName("Income"); - MyMoneyAccount income(stdAccNames[stdAccIncome], acc_i); - - MyMoneyAccount acc_q; - acc_q.setAccountType(eMyMoney::Account::Type::Equity); - acc_q.setName("Equity"); - MyMoneyAccount equity(stdAccNames[stdAccEquity], acc_q); - - QMap map; - map[stdAccNames[stdAccAsset]] = asset; - map[stdAccNames[stdAccLiability]] = liability; - map[stdAccNames[stdAccIncome]] = income; - map[stdAccNames[stdAccExpense]] = expense; - map[stdAccNames[stdAccEquity]] = equity; - // load account list with initial accounts - d->m_accountList = map; +MyMoneyStorageMgr::MyMoneyStorageMgr() : + d_ptr(new MyMoneyStorageMgrPrivate(this)) +{ + Q_D(MyMoneyStorageMgr); + d->init(); } -MyMoneySeqAccessMgr::~MyMoneySeqAccessMgr() +MyMoneyStorageMgr::~MyMoneyStorageMgr() { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); delete d; } -MyMoneyPayee MyMoneySeqAccessMgr::user() const +MyMoneyPayee MyMoneyStorageMgr::user() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_user; } -QDate MyMoneySeqAccessMgr::creationDate() const +QDate MyMoneyStorageMgr::creationDate() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_creationDate; } -QDate MyMoneySeqAccessMgr::lastModificationDate() const +QDate MyMoneyStorageMgr::lastModificationDate() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_lastModificationDate; } -uint MyMoneySeqAccessMgr::currentFixVersion() const +uint MyMoneyStorageMgr::currentFixVersion() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_currentFixVersion; } -uint MyMoneySeqAccessMgr::fileFixVersion() const +uint MyMoneyStorageMgr::fileFixVersion() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_fileFixVersion; } -void MyMoneySeqAccessMgr::setUser(const MyMoneyPayee& user) +void MyMoneyStorageMgr::setUser(const MyMoneyPayee& user) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_user = user; d->touch(); } -void MyMoneySeqAccessMgr::setCreationDate(const QDate& val) +void MyMoneyStorageMgr::setCreationDate(const QDate& val) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_creationDate = val; d->touch(); } -void MyMoneySeqAccessMgr::setLastModificationDate(const QDate& val) +void MyMoneyStorageMgr::setLastModificationDate(const QDate& val) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_lastModificationDate = val; d->m_dirty = false; } -void MyMoneySeqAccessMgr::setFileFixVersion(uint v) +void MyMoneyStorageMgr::setFileFixVersion(uint v) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_fileFixVersion = v; } -//MyMoneySeqAccessMgr const * MyMoneySeqAccessMgr::duplicate() -//{ -// auto that = new MyMoneySeqAccessMgr(); -// *that = *this; -// return that; -//} - -/** -* This method is used to get a SQL reader for subsequent database access - */ -QExplicitlySharedDataPointer MyMoneySeqAccessMgr::connectToDatabase -(const QUrl& /*url*/) -{ - return QExplicitlySharedDataPointer (); -} - -void MyMoneySeqAccessMgr::fillStorage() -{ - -} - -bool MyMoneySeqAccessMgr::isStandardAccount(const QString& id) const +bool MyMoneyStorageMgr::isStandardAccount(const QString& id) const { return id == stdAccNames[stdAccLiability] || id == stdAccNames[stdAccAsset] || id == stdAccNames[stdAccExpense] || id == stdAccNames[stdAccIncome] || id == stdAccNames[stdAccEquity]; } -void MyMoneySeqAccessMgr::setAccountName(const QString& id, const QString& name) +void MyMoneyStorageMgr::setAccountName(const QString& id, const QString& name) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); if (!isStandardAccount(id)) throw MYMONEYEXCEPTION("Only standard accounts can be modified using setAccountName()"); auto acc = d->m_accountList[id]; acc.setName(name); d->m_accountList.modify(acc.id(), acc); } -MyMoneyAccount MyMoneySeqAccessMgr::account(const QString& id) const +MyMoneyAccount MyMoneyStorageMgr::account(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); // locate the account and if present, return it's data - if (d->m_accountList.find(id) != d->m_accountList.end()) - return d->m_accountList[id]; + if (d->m_accountList.find(id) != d->m_accountList.end()) { + auto acc = d->m_accountList[id]; + // is that needed at all? + if (acc.fraction() == -1) { + const auto& sec = security(acc.currencyId()); + acc.fraction(sec); + } + return acc; + } // throw an exception, if it does not exist const auto msg = QString::fromLatin1("Unknown account id '%1'").arg(id); throw MYMONEYEXCEPTION(msg); } -void MyMoneySeqAccessMgr::accountList(QList& list) const +void MyMoneyStorageMgr::accountList(QList& list) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator it; for (it = d->m_accountList.begin(); it != d->m_accountList.end(); ++it) { if (!isStandardAccount((*it).id())) { list.append(*it); } } } -void MyMoneySeqAccessMgr::addAccount(MyMoneyAccount& account) +void MyMoneyStorageMgr::addAccount(MyMoneyAccount& account) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // create the account. - MyMoneyAccount newAccount(nextAccountID(), account); + MyMoneyAccount newAccount(d->nextAccountID(), account); d->m_accountList.insert(newAccount.id(), newAccount); account = newAccount; } -void MyMoneySeqAccessMgr::addPayee(MyMoneyPayee& payee) +void MyMoneyStorageMgr::addPayee(MyMoneyPayee& payee) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // create the payee - MyMoneyPayee newPayee(nextPayeeID(), payee); + MyMoneyPayee newPayee(d->nextPayeeID(), payee); d->m_payeeList.insert(newPayee.id(), newPayee); payee = newPayee; } /** * @brief Add onlineJob to storage * @param job caller stays owner of the object, but id will be set */ -void MyMoneySeqAccessMgr::addOnlineJob(onlineJob &job) +void MyMoneyStorageMgr::addOnlineJob(onlineJob &job) { - Q_D(MyMoneySeqAccessMgr); - onlineJob newJob = onlineJob(nextOnlineJobID(), job); + Q_D(MyMoneyStorageMgr); + onlineJob newJob = onlineJob(d->nextOnlineJobID(), job); d->m_onlineJobList.insert(newJob.id(), newJob); job = newJob; } -void MyMoneySeqAccessMgr::removeOnlineJob(const onlineJob& job) +void MyMoneyStorageMgr::removeOnlineJob(const onlineJob& job) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); if (!d->m_onlineJobList.contains(job.id())) { throw MYMONEYEXCEPTION("Unknown onlineJob '" + job.id() + "' should be removed."); } d->m_onlineJobList.remove(job.id()); } -void MyMoneySeqAccessMgr::modifyOnlineJob(const onlineJob &job) +void MyMoneyStorageMgr::modifyOnlineJob(const onlineJob &job) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator iter = d->m_onlineJobList.find(job.id()); if (iter == d->m_onlineJobList.end()) { throw MYMONEYEXCEPTION("Got unknown onlineJob '" + job.id() + "' for modifying"); } onlineJob oldJob = iter.value(); d->m_onlineJobList.modify((*iter).id(), job); } -onlineJob MyMoneySeqAccessMgr::getOnlineJob(const QString& id) const +onlineJob MyMoneyStorageMgr::getOnlineJob(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); if (d->m_onlineJobList.contains(id)) { return d->m_onlineJobList[id]; } throw MYMONEYEXCEPTION("Unknown online Job '" + id + '\''); } -ulong MyMoneySeqAccessMgr::onlineJobId() const +ulong MyMoneyStorageMgr::onlineJobId() const { return 1; } -MyMoneyPayee MyMoneySeqAccessMgr::payee(const QString& id) const +MyMoneyPayee MyMoneyStorageMgr::payee(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_payeeList.find(id); if (it == d->m_payeeList.end()) throw MYMONEYEXCEPTION("Unknown payee '" + id + '\''); return *it; } -MyMoneyPayee MyMoneySeqAccessMgr::payeeByName(const QString& payee) const +MyMoneyPayee MyMoneyStorageMgr::payeeByName(const QString& payee) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); if (payee.isEmpty()) return MyMoneyPayee::null; QMap::ConstIterator it_p; for (it_p = d->m_payeeList.begin(); it_p != d->m_payeeList.end(); ++it_p) { if ((*it_p).name() == payee) { return *it_p; } } throw MYMONEYEXCEPTION("Unknown payee '" + payee + '\''); } -void MyMoneySeqAccessMgr::modifyPayee(const MyMoneyPayee& payee) +void MyMoneyStorageMgr::modifyPayee(const MyMoneyPayee& payee) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_payeeList.find(payee.id()); if (it == d->m_payeeList.end()) { QString msg = "Unknown payee '" + payee.id() + '\''; throw MYMONEYEXCEPTION(msg); } d->m_payeeList.modify((*it).id(), payee); } -void MyMoneySeqAccessMgr::removePayee(const MyMoneyPayee& payee) +void MyMoneyStorageMgr::removePayee(const MyMoneyPayee& payee) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it_t; QMap::ConstIterator it_s; QMap::ConstIterator it_p; it_p = d->m_payeeList.find(payee.id()); if (it_p == d->m_payeeList.end()) { QString msg = "Unknown payee '" + payee.id() + '\''; throw MYMONEYEXCEPTION(msg); } // scan all transactions to check if the payee is still referenced for (it_t = d->m_transactionList.begin(); it_t != d->m_transactionList.end(); ++it_t) { if ((*it_t).hasReferenceTo(payee.id())) { throw MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("transaction")); } } // check referential integrity in schedules for (it_s = d->m_scheduleList.begin(); it_s != d->m_scheduleList.end(); ++it_s) { if ((*it_s).hasReferenceTo(payee.id())) { throw MYMONEYEXCEPTION(QString("Cannot remove payee that is still referenced to a %1").arg("schedule")); } } // remove any reference to report and/or budget d->removeReferences(payee.id()); d->m_payeeList.remove((*it_p).id()); } -QList MyMoneySeqAccessMgr::payeeList() const +QList MyMoneyStorageMgr::payeeList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_payeeList.values(); } -void MyMoneySeqAccessMgr::addTag(MyMoneyTag& tag) +void MyMoneyStorageMgr::addTag(MyMoneyTag& tag) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // create the tag - MyMoneyTag newTag(nextTagID(), tag); + MyMoneyTag newTag(d->nextTagID(), tag); d->m_tagList.insert(newTag.id(), newTag); tag = newTag; } -MyMoneyTag MyMoneySeqAccessMgr::tag(const QString& id) const +MyMoneyTag MyMoneyStorageMgr::tag(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_tagList.find(id); if (it == d->m_tagList.end()) throw MYMONEYEXCEPTION("Unknown tag '" + id + '\''); return *it; } -MyMoneyTag MyMoneySeqAccessMgr::tagByName(const QString& tag) const +MyMoneyTag MyMoneyStorageMgr::tagByName(const QString& tag) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); if (tag.isEmpty()) return MyMoneyTag::null; QMap::ConstIterator it_ta; for (it_ta = d->m_tagList.begin(); it_ta != d->m_tagList.end(); ++it_ta) { if ((*it_ta).name() == tag) { return *it_ta; } } throw MYMONEYEXCEPTION("Unknown tag '" + tag + '\''); } -void MyMoneySeqAccessMgr::modifyTag(const MyMoneyTag& tag) +void MyMoneyStorageMgr::modifyTag(const MyMoneyTag& tag) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_tagList.find(tag.id()); if (it == d->m_tagList.end()) { QString msg = "Unknown tag '" + tag.id() + '\''; throw MYMONEYEXCEPTION(msg); } d->m_tagList.modify((*it).id(), tag); } -void MyMoneySeqAccessMgr::removeTag(const MyMoneyTag& tag) +void MyMoneyStorageMgr::removeTag(const MyMoneyTag& tag) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it_t; QMap::ConstIterator it_s; QMap::ConstIterator it_ta; it_ta = d->m_tagList.find(tag.id()); if (it_ta == d->m_tagList.end()) { QString msg = "Unknown tag '" + tag.id() + '\''; throw MYMONEYEXCEPTION(msg); } // scan all transactions to check if the tag is still referenced for (it_t = d->m_transactionList.begin(); it_t != d->m_transactionList.end(); ++it_t) { if ((*it_t).hasReferenceTo(tag.id())) { throw MYMONEYEXCEPTION(QString("Cannot remove tag that is still referenced to a %1").arg("transaction")); } } // check referential integrity in schedules for (it_s = d->m_scheduleList.begin(); it_s != d->m_scheduleList.end(); ++it_s) { if ((*it_s).hasReferenceTo(tag.id())) { throw MYMONEYEXCEPTION(QString("Cannot remove tag that is still referenced to a %1").arg("schedule")); } } // remove any reference to report and/or budget d->removeReferences(tag.id()); d->m_tagList.remove((*it_ta).id()); } -QList MyMoneySeqAccessMgr::tagList() const +QList MyMoneyStorageMgr::tagList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_tagList.values(); } -void MyMoneySeqAccessMgr::addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) +void MyMoneyStorageMgr::addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator theParent; QMap::ConstIterator theChild; theParent = d->m_accountList.find(parent.id()); if (theParent == d->m_accountList.end()) { QString msg = "Unknown parent account '"; msg += parent.id() + '\''; throw MYMONEYEXCEPTION(msg); } theChild = d->m_accountList.find(account.id()); if (theChild == d->m_accountList.end()) { QString msg = "Unknown child account '"; msg += account.id() + '\''; throw MYMONEYEXCEPTION(msg); } auto acc = *theParent; acc.addAccountId(account.id()); d->m_accountList.modify(acc.id(), acc); parent = acc; acc = *theChild; acc.setParentAccountId(parent.id()); d->m_accountList.modify(acc.id(), acc); account = acc; } -void MyMoneySeqAccessMgr::addInstitution(MyMoneyInstitution& institution) +void MyMoneyStorageMgr::addInstitution(MyMoneyInstitution& institution) { - Q_D(MyMoneySeqAccessMgr); - MyMoneyInstitution newInstitution(nextInstitutionID(), institution); + Q_D(MyMoneyStorageMgr); + MyMoneyInstitution newInstitution(d->nextInstitutionID(), institution); d->m_institutionList.insert(newInstitution.id(), newInstitution); // return new data institution = newInstitution; } -uint MyMoneySeqAccessMgr::transactionCount(const QString& account) const +uint MyMoneyStorageMgr::transactionCount(const QString& account) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); uint cnt = 0; if (account.length() == 0) { cnt = d->m_transactionList.count(); } else { // scan all transactions foreach (const auto transaction, d->m_transactionList) { // scan all splits of this transaction auto found = false; foreach (const auto split, transaction.splits()) { // is it a split in our account? if (split.accountId() == account) { // since a transaction can only have one split referencing // each account, we're done with the splits here! found = true; break; } } // if no split contains the account id, continue with the // next transaction if (!found) continue; // otherwise count it ++cnt; } } return cnt; } -QMap MyMoneySeqAccessMgr::transactionCountMap() const +QMap MyMoneyStorageMgr::transactionCountMap() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap map; // scan all transactions foreach (const auto transaction, d->m_transactionList) { // scan all splits of this transaction foreach (const auto split, transaction.splits()) { map[split.accountId()]++; } } return map; } -uint MyMoneySeqAccessMgr::institutionCount() const +uint MyMoneyStorageMgr::institutionCount() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_institutionList.count(); } -uint MyMoneySeqAccessMgr::accountCount() const +uint MyMoneyStorageMgr::accountCount() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_accountList.count(); } -void MyMoneySeqAccessMgr::addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate) +void MyMoneyStorageMgr::addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // perform some checks to see that the transaction stuff is OK. For // now we assume that // * no ids are assigned // * the date valid (must not be empty) // * the referenced accounts in the splits exist // first perform all the checks if (!transaction.id().isEmpty()) throw MYMONEYEXCEPTION("transaction already contains an id"); if (!transaction.postDate().isValid()) throw MYMONEYEXCEPTION("invalid post date"); // now check the splits foreach (const auto split, transaction.splits()) { // the following lines will throw an exception if the // account or payee do not exist account(split.accountId()); if (!split.payeeId().isEmpty()) payee(split.payeeId()); } - MyMoneyTransaction newTransaction(nextTransactionID(), transaction); + MyMoneyTransaction newTransaction(d->nextTransactionID(), transaction); QString key = newTransaction.uniqueSortKey(); d->m_transactionList.insert(key, newTransaction); d->m_transactionKeys.insert(newTransaction.id(), key); transaction = newTransaction; // adjust the balance of all affected accounts foreach (const auto split, transaction.splits()) { auto acc = d->m_accountList[split.accountId()]; d->adjustBalance(acc, split, false); if (!skipAccountUpdate) { acc.touch(); } d->m_accountList.modify(acc.id(), acc); } } -bool MyMoneySeqAccessMgr::hasActiveSplits(const QString& id) const +bool MyMoneyStorageMgr::hasActiveSplits(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator it; for (it = d->m_transactionList.begin(); it != d->m_transactionList.end(); ++it) { if ((*it).accountReferenced(id)) { return true; } } return false; } -MyMoneyInstitution MyMoneySeqAccessMgr::institution(const QString& id) const +MyMoneyInstitution MyMoneyStorageMgr::institution(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator pos; pos = d->m_institutionList.find(id); if (pos != d->m_institutionList.end()) return *pos; throw MYMONEYEXCEPTION("unknown institution"); } -bool MyMoneySeqAccessMgr::dirty() const +bool MyMoneyStorageMgr::dirty() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_dirty; } -void MyMoneySeqAccessMgr::setDirty() +void MyMoneyStorageMgr::setDirty() { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_dirty = true; } -QList MyMoneySeqAccessMgr::institutionList() const +QList MyMoneyStorageMgr::institutionList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_institutionList.values(); } -void MyMoneySeqAccessMgr::modifyAccount(const MyMoneyAccount& account, bool skipCheck) +void MyMoneyStorageMgr::modifyAccount(const MyMoneyAccount& account, bool skipCheck) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator pos; // locate the account in the file global pool pos = d->m_accountList.find(account.id()); if (pos != d->m_accountList.end()) { // check if the new info is based on the old one. // this is the case, when the file and the id // as well as the type are equal. if (((*pos).parentAccountId() == account.parentAccountId() && ((*pos).accountType() == account.accountType() || ((*pos).isLiquidAsset() && account.isLiquidAsset()))) || skipCheck == true) { // make sure that all the referenced objects exist if (!account.institutionId().isEmpty()) institution(account.institutionId()); foreach (const auto sAccount, account.accountList()) this->account(sAccount); // update information in account list d->m_accountList.modify(account.id(), account); } else throw MYMONEYEXCEPTION("Invalid information for update"); } else throw MYMONEYEXCEPTION("Unknown account id"); } -void MyMoneySeqAccessMgr::modifyInstitution(const MyMoneyInstitution& institution) +void MyMoneyStorageMgr::modifyInstitution(const MyMoneyInstitution& institution) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator pos; // locate the institution in the file global pool pos = d->m_institutionList.find(institution.id()); if (pos != d->m_institutionList.end()) { d->m_institutionList.modify(institution.id(), institution); } else throw MYMONEYEXCEPTION("unknown institution"); } -void MyMoneySeqAccessMgr::modifyTransaction(const MyMoneyTransaction& transaction) +void MyMoneyStorageMgr::modifyTransaction(const MyMoneyTransaction& transaction) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // perform some checks to see that the transaction stuff is OK. For // now we assume that // * ids are assigned // * the pointer to the MyMoneyFile object is not 0 // * the date valid (must not be empty) // * the splits must have valid account ids // first perform all the checks if (transaction.id().isEmpty() // || transaction.file() != this || !transaction.postDate().isValid()) throw MYMONEYEXCEPTION("invalid transaction to be modified"); // now check the splits foreach (const auto split, transaction.splits()) { // the following lines will throw an exception if the // account or payee do not exist account(split.accountId()); if (!split.payeeId().isEmpty()) payee(split.payeeId()); foreach (const auto tagId, split.tagIdList()) { if (!tagId.isEmpty()) tag(tagId); } } // new data seems to be ok. find old version of transaction // in our pool. Throw exception if unknown. if (!d->m_transactionKeys.contains(transaction.id())) throw MYMONEYEXCEPTION("invalid transaction id"); QString oldKey = d->m_transactionKeys[transaction.id()]; if (!d->m_transactionList.contains(oldKey)) throw MYMONEYEXCEPTION("invalid transaction key"); QMap::ConstIterator it_t; it_t = d->m_transactionList.find(oldKey); if (it_t == d->m_transactionList.end()) throw MYMONEYEXCEPTION("invalid transaction key"); foreach (const auto split, (*it_t).splits()) { auto acc = d->m_accountList[split.accountId()]; // we only need to adjust non-investment accounts here // as for investment accounts the balance will be recalculated // after the transaction has been added. if (!acc.isInvest()) { d->adjustBalance(acc, split, true); acc.touch(); d->m_accountList.modify(acc.id(), acc); } } // remove old transaction from lists d->m_transactionList.remove(oldKey); // add new transaction to lists QString newKey = transaction.uniqueSortKey(); d->m_transactionList.insert(newKey, transaction); d->m_transactionKeys.modify(transaction.id(), newKey); // adjust account balances foreach (const auto split, transaction.splits()) { auto acc = d->m_accountList[split.accountId()]; d->adjustBalance(acc, split, false); acc.touch(); d->m_accountList.modify(acc.id(), acc); } } -void MyMoneySeqAccessMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent) +void MyMoneyStorageMgr::reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->reparentAccount(account, parent, true); } -void MyMoneySeqAccessMgr::close() +void MyMoneyStorageMgr::close() { } -void MyMoneySeqAccessMgr::removeTransaction(const MyMoneyTransaction& transaction) +void MyMoneyStorageMgr::removeTransaction(const MyMoneyTransaction& transaction) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // first perform all the checks if (transaction.id().isEmpty()) throw MYMONEYEXCEPTION("invalid transaction to be deleted"); QMap::ConstIterator it_k; QMap::ConstIterator it_t; it_k = d->m_transactionKeys.find(transaction.id()); if (it_k == d->m_transactionKeys.end()) throw MYMONEYEXCEPTION("invalid transaction to be deleted"); it_t = d->m_transactionList.find(*it_k); if (it_t == d->m_transactionList.end()) throw MYMONEYEXCEPTION("invalid transaction key"); // keep a copy so that we still have the data after removal MyMoneyTransaction t(*it_t); // FIXME: check if any split is frozen and throw exception // remove the transaction from the two lists d->m_transactionList.remove(*it_k); d->m_transactionKeys.remove(transaction.id()); // scan the splits and collect all accounts that need // to be updated after the removal of this transaction foreach (const auto split, t.splits()) { auto acc = d->m_accountList[split.accountId()]; d->adjustBalance(acc, split, true); acc.touch(); d->m_accountList.modify(acc.id(), acc); } } -void MyMoneySeqAccessMgr::removeAccount(const MyMoneyAccount& account) +void MyMoneyStorageMgr::removeAccount(const MyMoneyAccount& account) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); MyMoneyAccount parent; // check that the account and it's parent exist // this will throw an exception if the id is unknown - MyMoneySeqAccessMgr::account(account.id()); - parent = MyMoneySeqAccessMgr::account(account.parentAccountId()); + MyMoneyStorageMgr::account(account.id()); + parent = MyMoneyStorageMgr::account(account.parentAccountId()); // check that it's not one of the standard account groups if (isStandardAccount(account.id())) throw MYMONEYEXCEPTION("Unable to remove the standard account groups"); if (hasActiveSplits(account.id())) { throw MYMONEYEXCEPTION("Unable to remove account with active splits"); } // re-parent all sub-ordinate accounts to the parent of the account // to be deleted. First round check that all accounts exist, second // round do the re-parenting. foreach (const auto accountID, account.accountList()) - MyMoneySeqAccessMgr::account(accountID); + MyMoneyStorageMgr::account(accountID); // if one of the accounts did not exist, an exception had been // thrown and we would not make it until here. QMap::ConstIterator it_a; QMap::ConstIterator it_p; // locate the account in the file global pool it_a = d->m_accountList.find(account.id()); if (it_a == d->m_accountList.end()) throw MYMONEYEXCEPTION("Internal error: account not found in list"); it_p = d->m_accountList.find(parent.id()); if (it_p == d->m_accountList.end()) throw MYMONEYEXCEPTION("Internal error: parent account not found in list"); if (!account.institutionId().isEmpty()) throw MYMONEYEXCEPTION("Cannot remove account still attached to an institution"); d->removeReferences(account.id()); // FIXME: check referential integrity for the account to be removed // check if the new info is based on the old one. // this is the case, when the file and the id // as well as the type are equal. if ((*it_a).id() == account.id() && (*it_a).accountType() == account.accountType()) { // second round over sub-ordinate accounts: do re-parenting // but only if the list contains at least one entry // FIXME: move this logic to MyMoneyFile foreach (const auto accountID, (*it_a).accountList()) { - MyMoneyAccount acc(MyMoneySeqAccessMgr::account(accountID)); + MyMoneyAccount acc(MyMoneyStorageMgr::account(accountID)); d->reparentAccount(acc, parent, false); } // remove account from parent's list parent.removeAccountId(account.id()); d->m_accountList.modify(parent.id(), parent); // remove account from the global account pool d->m_accountList.remove(account.id()); } } -void MyMoneySeqAccessMgr::removeInstitution(const MyMoneyInstitution& institution) +void MyMoneyStorageMgr::removeInstitution(const MyMoneyInstitution& institution) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it_i; it_i = d->m_institutionList.find(institution.id()); if (it_i != d->m_institutionList.end()) { d->m_institutionList.remove(institution.id()); } else throw MYMONEYEXCEPTION("invalid institution"); } -void MyMoneySeqAccessMgr::transactionList(QList& list, MyMoneyTransactionFilter& filter) const +void MyMoneyStorageMgr::transactionList(QList& list, MyMoneyTransactionFilter& filter) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); list.clear(); for (const auto& transaction : d->m_transactionList) { // This code is used now. It adds the transaction to the list for // each matching split exactly once. This allows to show information // about different splits in the same register view (e.g. search result) // // I have no idea, if this has some impact on the functionality. So far, // I could not see it. (ipwizard 9/5/2003) const auto cnt = filter.matchingSplitsCount(transaction); for (uint i = 0; i < cnt; ++i) list.append(transaction); } } -void MyMoneySeqAccessMgr::transactionList(QList< QPair >& list, MyMoneyTransactionFilter& filter) const +void MyMoneyStorageMgr::transactionList(QList< QPair >& list, MyMoneyTransactionFilter& filter) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); list.clear(); for (const auto& transaction : d->m_transactionList) for (const auto& split : filter.matchingSplits(transaction)) list.append(qMakePair(transaction, split)); } -QList MyMoneySeqAccessMgr::transactionList(MyMoneyTransactionFilter& filter) const +QList MyMoneyStorageMgr::transactionList(MyMoneyTransactionFilter& filter) const { QList list; transactionList(list, filter); return list; } -QList MyMoneySeqAccessMgr::onlineJobList() const +QList MyMoneyStorageMgr::onlineJobList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_onlineJobList.values(); } -QList< MyMoneyCostCenter > MyMoneySeqAccessMgr::costCenterList() const +QList< MyMoneyCostCenter > MyMoneyStorageMgr::costCenterList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_costCenterList.values(); } -MyMoneyCostCenter MyMoneySeqAccessMgr::costCenter(const QString& id) const +MyMoneyCostCenter MyMoneyStorageMgr::costCenter(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); if (!d->m_costCenterList.contains(id)) { QString msg = QString("Invalid cost center id '%1'").arg(id); throw MYMONEYEXCEPTION(msg); } return d->m_costCenterList[id]; } -bool MyMoneySeqAccessMgr::isDuplicateTransaction(const QString& id) const +bool MyMoneyStorageMgr::isDuplicateTransaction(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_transactionKeys.contains(id); } -MyMoneyTransaction MyMoneySeqAccessMgr::transaction(const QString& id) const +MyMoneyTransaction MyMoneyStorageMgr::transaction(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); // get the full key of this transaction, throw exception // if it's invalid (unknown) if (!d->m_transactionKeys.contains(id)) { QString msg = QString("Invalid transaction id '%1'").arg(id); throw MYMONEYEXCEPTION(msg); } // check if this key is in the list, throw exception if not QString key = d->m_transactionKeys[id]; if (!d->m_transactionList.contains(key)) { QString msg = QString("Invalid transaction key '%1'").arg(key); throw MYMONEYEXCEPTION(msg); } return d->m_transactionList[key]; } -MyMoneyTransaction MyMoneySeqAccessMgr::transaction(const QString& account, const int idx) const +MyMoneyTransaction MyMoneyStorageMgr::transaction(const QString& account, const int idx) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); /* removed with MyMoneyAccount::Transaction QMap::ConstIterator acc; // find account object in list, throw exception if unknown acc = m_accountList.find(account); if(acc == m_accountList.end()) throw MYMONEYEXCEPTION("unknown account id"); // get the transaction info from the account MyMoneyAccount::Transaction t = (*acc).transaction(idx); // return the transaction, throw exception if not found return transaction(t.transactionID()); */ // new implementation if the above code does not work anymore QList list; auto acc = d->m_accountList[account]; MyMoneyTransactionFilter filter; if (acc.accountGroup() == eMyMoney::Account::Type::Income || acc.accountGroup() == eMyMoney::Account::Type::Expense) filter.addCategory(account); else filter.addAccount(account); transactionList(list, filter); if (idx < 0 || idx >= static_cast(list.count())) throw MYMONEYEXCEPTION("Unknown idx for transaction"); return transaction(list[idx].id()); } -MyMoneyMoney MyMoneySeqAccessMgr::balance(const QString& id, const QDate& date) const +MyMoneyMoney MyMoneyStorageMgr::balance(const QString& id, const QDate& date) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); if (!d->m_accountList.contains(id)) throw MYMONEYEXCEPTION(QString("Unknown account id '%1'").arg(id)); // the balance of all transactions for this account has // been requested. no need to calculate anything as we // have this number with the account object already. if (!date.isValid()) return d->m_accountList[id].balance(); else return d->calculateBalance(id, date); } - -MyMoneyMoney MyMoneySeqAccessMgr::totalBalance(const QString& id, const QDate& date) const +MyMoneyMoney MyMoneyStorageMgr::totalBalance(const QString& id, const QDate& date) const { MyMoneyMoney result(balance(id, date)); foreach (const auto sAccount, account(id).accountList()) result += totalBalance(sAccount, date); return result; } -MyMoneyAccount MyMoneySeqAccessMgr::liability() const { +MyMoneyAccount MyMoneyStorageMgr::liability() const { return account(stdAccNames[stdAccLiability]); } -MyMoneyAccount MyMoneySeqAccessMgr::asset() const { +MyMoneyAccount MyMoneyStorageMgr::asset() const { return account(stdAccNames[stdAccAsset]); } -MyMoneyAccount MyMoneySeqAccessMgr::expense() const { +MyMoneyAccount MyMoneyStorageMgr::expense() const { return account(stdAccNames[stdAccExpense]); } -MyMoneyAccount MyMoneySeqAccessMgr::income() const { +MyMoneyAccount MyMoneyStorageMgr::income() const { return account(stdAccNames[stdAccIncome]); } -MyMoneyAccount MyMoneySeqAccessMgr::equity() const { +MyMoneyAccount MyMoneyStorageMgr::equity() const { return account(stdAccNames[stdAccEquity]); } -void MyMoneySeqAccessMgr::loadAccounts(const QMap& map) +void MyMoneyStorageMgr::loadAccounts(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_accountList = map; // scan the map to identify the last used id QMap::const_iterator it_a; QString lastId(""); for (it_a = map.begin(); it_a != map.end(); ++it_a) { if (!isStandardAccount((*it_a).id()) && ((*it_a).id() > lastId)) lastId = (*it_a).id(); } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextAccountID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadTransactions(const QMap& map) +void MyMoneyStorageMgr::loadTransactions(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_transactionList = map; // now fill the key map and // identify the last used id QString lastId(""); QMap keys; QMap::ConstIterator it_t; for (it_t = map.constBegin(); it_t != map.constEnd(); ++it_t) { keys[(*it_t).id()] = it_t.key(); if ((*it_t).id() > lastId) lastId = (*it_t).id(); } d->m_transactionKeys = keys; // determine highest used ID so far int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextTransactionID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadInstitutions(const QMap& map) +void MyMoneyStorageMgr::loadInstitutions(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_institutionList = map; // scan the map to identify the last used id QMap::const_iterator it_i; QString lastId(""); for (it_i = map.begin(); it_i != map.end(); ++it_i) { if ((*it_i).id() > lastId) lastId = (*it_i).id(); } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextInstitutionID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadPayees(const QMap& map) +void MyMoneyStorageMgr::loadPayees(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_payeeList = map; // scan the map to identify the last used id QMap::const_iterator it_p; QString lastId(""); for (it_p = map.begin(); it_p != map.end(); ++it_p) { if ((*it_p).id().length() <= PAYEE_ID_SIZE + 1) { if ((*it_p).id() > lastId) lastId = (*it_p).id(); } else { } } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextPayeeID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadTags(const QMap& map) +void MyMoneyStorageMgr::loadTags(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_tagList = map; // scan the map to identify the last used id QMap::const_iterator it_ta; QString lastId(""); for (it_ta = map.begin(); it_ta != map.end(); ++it_ta) { if ((*it_ta).id().length() <= TAG_ID_SIZE + 1) { if ((*it_ta).id() > lastId) lastId = (*it_ta).id(); } else { } } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextTagID = lastId.mid(pos).toUInt(); } } -void MyMoneySeqAccessMgr::loadSecurities(const QMap& map) +void MyMoneyStorageMgr::loadSecurities(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_securitiesList = map; // scan the map to identify the last used id QMap::const_iterator it_s; QString lastId(""); for (it_s = map.begin(); it_s != map.end(); ++it_s) { if ((*it_s).id() > lastId) lastId = (*it_s).id(); } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextSecurityID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadCurrencies(const QMap& map) +void MyMoneyStorageMgr::loadCurrencies(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_currencyList = map; } -void MyMoneySeqAccessMgr::loadPrices(const MyMoneyPriceList& list) +void MyMoneyStorageMgr::loadPrices(const MyMoneyPriceList& list) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_priceList = list; } -void MyMoneySeqAccessMgr::loadOnlineJobs(const QMap< QString, onlineJob >& onlineJobs) +void MyMoneyStorageMgr::loadOnlineJobs(const QMap< QString, onlineJob >& onlineJobs) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_onlineJobList = onlineJobs; QString lastId(""); const QMap< QString, onlineJob >::const_iterator end = onlineJobs.constEnd(); for (QMap< QString, onlineJob >::const_iterator iter = onlineJobs.constBegin(); iter != end; ++iter) { if ((*iter).id() > lastId) lastId = (*iter).id(); } const int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextOnlineJobID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadCostCenters(const QMap< QString, MyMoneyCostCenter >& costCenters) +void MyMoneyStorageMgr::loadCostCenters(const QMap< QString, MyMoneyCostCenter >& costCenters) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_costCenterList = costCenters; // scan the map to identify the last used id QMap::const_iterator it_s; QString lastId; for (it_s = costCenters.constBegin(); it_s != costCenters.constEnd(); ++it_s) { if ((*it_s).id() > lastId) lastId = (*it_s).id(); } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextCostCenterID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadAccountId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextAccountID = id; -} - -void MyMoneySeqAccessMgr::loadTransactionId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextTransactionID = id; -} - -void MyMoneySeqAccessMgr::loadPayeeId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextPayeeID = id; -} - -void MyMoneySeqAccessMgr::loadTagId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextTagID = id; -} - -void MyMoneySeqAccessMgr::loadInstitutionId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextInstitutionID = id; -} - -void MyMoneySeqAccessMgr::loadSecurityId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextSecurityID = id; -} - -void MyMoneySeqAccessMgr::loadReportId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextReportID = id; -} - -void MyMoneySeqAccessMgr::loadBudgetId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextBudgetID = id; -} - -void MyMoneySeqAccessMgr::loadOnlineJobId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextOnlineJobID = id; -} - -void MyMoneySeqAccessMgr::loadCostCenterId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextCostCenterID = id; -} - -ulong MyMoneySeqAccessMgr::accountId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextAccountID; -} - -ulong MyMoneySeqAccessMgr::transactionId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextTransactionID; -} - -ulong MyMoneySeqAccessMgr::payeeId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextPayeeID; -} - -ulong MyMoneySeqAccessMgr::tagId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextTagID; -} - -ulong MyMoneySeqAccessMgr::institutionId() const +void MyMoneyStorageMgr::setValue(const QString& key, const QString& val) { - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextInstitutionID; -} - -ulong MyMoneySeqAccessMgr::scheduleId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextScheduleID; -} - -ulong MyMoneySeqAccessMgr::securityId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextSecurityID; -} - -ulong MyMoneySeqAccessMgr::reportId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextReportID; -} - -ulong MyMoneySeqAccessMgr::budgetId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextBudgetID; -} - -ulong MyMoneySeqAccessMgr::costCenterId() const -{ - Q_D(const MyMoneySeqAccessMgr); - return d->m_nextCostCenterID; -} - -QString MyMoneySeqAccessMgr::value(const QString& key) const -{ - return MyMoneyKeyValueContainer::value(key); -} - -void MyMoneySeqAccessMgr::setValue(const QString& key, const QString& val) -{ - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); MyMoneyKeyValueContainer::setValue(key, val); d->touch(); } -void MyMoneySeqAccessMgr::deletePair(const QString& key) +void MyMoneyStorageMgr::deletePair(const QString& key) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); MyMoneyKeyValueContainer::deletePair(key); d->touch(); } -QMap MyMoneySeqAccessMgr::pairs() const -{ - return MyMoneyKeyValueContainer::pairs(); -} -void MyMoneySeqAccessMgr::setPairs(const QMap& list) +void MyMoneyStorageMgr::setPairs(const QMap& list) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); MyMoneyKeyValueContainer::setPairs(list); d->touch(); } -void MyMoneySeqAccessMgr::addSchedule(MyMoneySchedule& sched) +void MyMoneyStorageMgr::addSchedule(MyMoneySchedule& sched) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // first perform all the checks if (!sched.id().isEmpty()) throw MYMONEYEXCEPTION("schedule already contains an id"); // The following will throw an exception when it fails sched.validate(false); - MyMoneySchedule newSched(nextScheduleID(), sched); + // it is expected in mymoneygenericstorage-test + const auto splits = sched.transaction().splits(); + for (const auto& split : splits) + if (!d->m_accountList.contains(split.accountId())) + throw MYMONEYEXCEPTION("bad account id"); + + MyMoneySchedule newSched(d->nextScheduleID(), sched); d->m_scheduleList.insert(newSched.id(), newSched); sched = newSched; } -void MyMoneySeqAccessMgr::modifySchedule(const MyMoneySchedule& sched) +void MyMoneyStorageMgr::modifySchedule(const MyMoneySchedule& sched) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_scheduleList.find(sched.id()); if (it == d->m_scheduleList.end()) { QString msg = "Unknown schedule '" + sched.id() + '\''; throw MYMONEYEXCEPTION(msg); } d->m_scheduleList.modify(sched.id(), sched); } -void MyMoneySeqAccessMgr::removeSchedule(const MyMoneySchedule& sched) +void MyMoneyStorageMgr::removeSchedule(const MyMoneySchedule& sched) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_scheduleList.find(sched.id()); if (it == d->m_scheduleList.end()) { QString msg = "Unknown schedule '" + sched.id() + '\''; throw MYMONEYEXCEPTION(msg); } // FIXME: check referential integrity for loan accounts d->m_scheduleList.remove(sched.id()); } -MyMoneySchedule MyMoneySeqAccessMgr::schedule(const QString& id) const +MyMoneySchedule MyMoneyStorageMgr::schedule(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator pos; // locate the schedule and if present, return it's data pos = d->m_scheduleList.find(id); if (pos != d->m_scheduleList.end()) return (*pos); // throw an exception, if it does not exist QString msg = "Unknown schedule id '" + id + '\''; throw MYMONEYEXCEPTION(msg); } -QList MyMoneySeqAccessMgr::scheduleList(const QString& accountId, +QList MyMoneyStorageMgr::scheduleList(const QString& accountId, eMyMoney::Schedule::Type type, eMyMoney::Schedule::Occurrence occurrence, eMyMoney::Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, bool overdue) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator pos; QList list; // qDebug("scheduleList()"); for (pos = d->m_scheduleList.begin(); pos != d->m_scheduleList.end(); ++pos) { // qDebug(" '%s'", qPrintable((*pos).id())); if (type != eMyMoney::Schedule::Type::Any) { if (type != (*pos).type()) { continue; } } if (occurrence != eMyMoney::Schedule::Occurrence::Any) { if (occurrence != (*pos).occurrence()) { continue; } } if (paymentType != eMyMoney::Schedule::PaymentType::Any) { if (paymentType != (*pos).paymentType()) { continue; } } if (!accountId.isEmpty()) { MyMoneyTransaction t = (*pos).transaction(); QList::ConstIterator it; QList splits; splits = t.splits(); for (it = splits.constBegin(); it != splits.constEnd(); ++it) { if ((*it).accountId() == accountId) break; } if (it == splits.constEnd()) { continue; } } if (startDate.isValid() && endDate.isValid()) { if ((*pos).paymentDates(startDate, endDate).count() == 0) { continue; } } if (startDate.isValid() && !endDate.isValid()) { if (!(*pos).nextPayment(startDate.addDays(-1)).isValid()) { continue; } } if (!startDate.isValid() && endDate.isValid()) { if ((*pos).startDate() > endDate) { continue; } } if (overdue) { if (!(*pos).isOverdue()) continue; } // qDebug("Adding '%s'", (*pos).name().toLatin1()); list << *pos; } return list; } -void MyMoneySeqAccessMgr::loadSchedules(const QMap& map) +void MyMoneyStorageMgr::loadSchedules(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_scheduleList = map; // scan the map to identify the last used id QMap::const_iterator it_s; QString lastId(""); for (it_s = map.begin(); it_s != map.end(); ++it_s) { if ((*it_s).id() > lastId) lastId = (*it_s).id(); } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextScheduleID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::loadScheduleId(ulong id) -{ - Q_D(MyMoneySeqAccessMgr); - d->m_nextScheduleID = id; -} - -QList MyMoneySeqAccessMgr::scheduleListEx(int scheduleTypes, +QList MyMoneyStorageMgr::scheduleListEx(int scheduleTypes, int scheduleOcurrences, int schedulePaymentTypes, QDate date, const QStringList& accounts) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); // qDebug("scheduleListEx"); QMap::ConstIterator pos; QList list; if (!date.isValid()) return list; for (pos = d->m_scheduleList.begin(); pos != d->m_scheduleList.end(); ++pos) { if (scheduleTypes && !(scheduleTypes & (int)(*pos).type())) continue; if (scheduleOcurrences && !(scheduleOcurrences & (int)(*pos).occurrence())) continue; if (schedulePaymentTypes && !(schedulePaymentTypes & (int)(*pos).paymentType())) continue; if ((*pos).paymentDates(date, date).count() == 0) continue; if ((*pos).isFinished()) continue; if ((*pos).hasRecordedPayment(date)) continue; if (accounts.count() > 0) { if (accounts.contains((*pos).account().id())) continue; } // qDebug("\tAdding '%s'", (*pos).name().toLatin1()); list << *pos; } return list; } -void MyMoneySeqAccessMgr::addSecurity(MyMoneySecurity& security) +void MyMoneyStorageMgr::addSecurity(MyMoneySecurity& security) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // create the account - MyMoneySecurity newSecurity(nextSecurityID(), security); + MyMoneySecurity newSecurity(d->nextSecurityID(), security); d->m_securitiesList.insert(newSecurity.id(), newSecurity); security = newSecurity; } -void MyMoneySeqAccessMgr::modifySecurity(const MyMoneySecurity& security) +void MyMoneyStorageMgr::modifySecurity(const MyMoneySecurity& security) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_securitiesList.find(security.id()); if (it == d->m_securitiesList.end()) { QString msg = "Unknown security '"; msg += security.id() + "' during modifySecurity()"; throw MYMONEYEXCEPTION(msg); } d->m_securitiesList.modify(security.id(), security); } -void MyMoneySeqAccessMgr::removeSecurity(const MyMoneySecurity& security) +void MyMoneyStorageMgr::removeSecurity(const MyMoneySecurity& security) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; // FIXME: check referential integrity it = d->m_securitiesList.find(security.id()); if (it == d->m_securitiesList.end()) { QString msg = "Unknown security '"; msg += security.id() + "' during removeSecurity()"; throw MYMONEYEXCEPTION(msg); } d->m_securitiesList.remove(security.id()); } -MyMoneySecurity MyMoneySeqAccessMgr::security(const QString& id) const +MyMoneySecurity MyMoneyStorageMgr::security(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator it = d->m_securitiesList.find(id); if (it != d->m_securitiesList.end()) { return it.value(); } return MyMoneySecurity(); } -QList MyMoneySeqAccessMgr::securityList() const +QList MyMoneyStorageMgr::securityList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); //qDebug("securityList: Security list size is %d, this=%8p", m_equitiesList.size(), (void*)this); return d->m_securitiesList.values(); } -void MyMoneySeqAccessMgr::addCurrency(const MyMoneySecurity& currency) +void MyMoneyStorageMgr::addCurrency(const MyMoneySecurity& currency) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_currencyList.find(currency.id()); if (it != d->m_currencyList.end()) { throw MYMONEYEXCEPTION(i18n("Cannot add currency with existing id %1", currency.id())); } d->m_currencyList.insert(currency.id(), currency); } -void MyMoneySeqAccessMgr::modifyCurrency(const MyMoneySecurity& currency) +void MyMoneyStorageMgr::modifyCurrency(const MyMoneySecurity& currency) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_currencyList.find(currency.id()); if (it == d->m_currencyList.end()) { throw MYMONEYEXCEPTION(i18n("Cannot modify currency with unknown id %1", currency.id())); } d->m_currencyList.modify(currency.id(), currency); } -void MyMoneySeqAccessMgr::removeCurrency(const MyMoneySecurity& currency) +void MyMoneyStorageMgr::removeCurrency(const MyMoneySecurity& currency) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; // FIXME: check referential integrity it = d->m_currencyList.find(currency.id()); if (it == d->m_currencyList.end()) { throw MYMONEYEXCEPTION(i18n("Cannot remove currency with unknown id %1", currency.id())); } d->m_currencyList.remove(currency.id()); } -MyMoneySecurity MyMoneySeqAccessMgr::currency(const QString& id) const +MyMoneySecurity MyMoneyStorageMgr::currency(const QString& id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); if (id.isEmpty()) { } QMap::ConstIterator it; it = d->m_currencyList.find(id); if (it == d->m_currencyList.end()) { throw MYMONEYEXCEPTION(i18n("Cannot retrieve currency with unknown id '%1'", id)); } return *it; } -QList MyMoneySeqAccessMgr::currencyList() const +QList MyMoneyStorageMgr::currencyList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_currencyList.values(); } -QList MyMoneySeqAccessMgr::reportList() const +QList MyMoneyStorageMgr::reportList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_reportList.values(); } -void MyMoneySeqAccessMgr::addReport(MyMoneyReport& report) +void MyMoneyStorageMgr::addReport(MyMoneyReport& report) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); if (!report.id().isEmpty()) throw MYMONEYEXCEPTION("report already contains an id"); - MyMoneyReport newReport(nextReportID(), report); + MyMoneyReport newReport(d->nextReportID(), report); d->m_reportList.insert(newReport.id(), newReport); report = newReport; } -void MyMoneySeqAccessMgr::loadReports(const QMap& map) +void MyMoneyStorageMgr::loadReports(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_reportList = map; // scan the map to identify the last used id QMap::const_iterator it_r; QString lastId(""); for (it_r = map.begin(); it_r != map.end(); ++it_r) { if ((*it_r).id() > lastId) lastId = (*it_r).id(); } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextReportID = lastId.mid(pos).toInt(); } } -void MyMoneySeqAccessMgr::modifyReport(const MyMoneyReport& report) +void MyMoneyStorageMgr::modifyReport(const MyMoneyReport& report) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_reportList.find(report.id()); if (it == d->m_reportList.end()) { QString msg = "Unknown report '" + report.id() + '\''; throw MYMONEYEXCEPTION(msg); } d->m_reportList.modify(report.id(), report); } -uint MyMoneySeqAccessMgr::countReports() const +uint MyMoneyStorageMgr::countReports() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_reportList.count(); } -MyMoneyReport MyMoneySeqAccessMgr::report(const QString& _id) const +MyMoneyReport MyMoneyStorageMgr::report(const QString& _id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_reportList[_id]; } -void MyMoneySeqAccessMgr::removeReport(const MyMoneyReport& report) +void MyMoneyStorageMgr::removeReport(const MyMoneyReport& report) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_reportList.find(report.id()); if (it == d->m_reportList.end()) { QString msg = "Unknown report '" + report.id() + '\''; throw MYMONEYEXCEPTION(msg); } d->m_reportList.remove(report.id()); } -QList MyMoneySeqAccessMgr::budgetList() const +QList MyMoneyStorageMgr::budgetList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_budgetList.values(); } -void MyMoneySeqAccessMgr::addBudget(MyMoneyBudget& budget) +void MyMoneyStorageMgr::addBudget(MyMoneyBudget& budget) { - Q_D(MyMoneySeqAccessMgr); - MyMoneyBudget newBudget(nextBudgetID(), budget); + Q_D(MyMoneyStorageMgr); + MyMoneyBudget newBudget(d->nextBudgetID(), budget); d->m_budgetList.insert(newBudget.id(), newBudget); budget = newBudget; } -void MyMoneySeqAccessMgr::loadBudgets(const QMap& map) +void MyMoneyStorageMgr::loadBudgets(const QMap& map) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_budgetList = map; // scan the map to identify the last used id QMap::const_iterator it_b; QString lastId(""); for (it_b = map.begin(); it_b != map.end(); ++it_b) { if ((*it_b).id() > lastId) lastId = (*it_b).id(); } int pos = lastId.indexOf(QRegExp("\\d+"), 0); if (pos != -1) { d->m_nextBudgetID = lastId.mid(pos).toInt(); } } -MyMoneyBudget MyMoneySeqAccessMgr::budgetByName(const QString& budget) const +MyMoneyBudget MyMoneyStorageMgr::budgetByName(const QString& budget) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); QMap::ConstIterator it_p; for (it_p = d->m_budgetList.begin(); it_p != d->m_budgetList.end(); ++it_p) { if ((*it_p).name() == budget) { return *it_p; } } throw MYMONEYEXCEPTION("Unknown budget '" + budget + '\''); } -void MyMoneySeqAccessMgr::modifyBudget(const MyMoneyBudget& budget) +void MyMoneyStorageMgr::modifyBudget(const MyMoneyBudget& budget) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_budgetList.find(budget.id()); if (it == d->m_budgetList.end()) { QString msg = "Unknown budget '" + budget.id() + '\''; throw MYMONEYEXCEPTION(msg); } d->m_budgetList.modify(budget.id(), budget); } -uint MyMoneySeqAccessMgr::countBudgets() const +uint MyMoneyStorageMgr::countBudgets() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_budgetList.count(); } -MyMoneyBudget MyMoneySeqAccessMgr::budget(const QString& _id) const +MyMoneyBudget MyMoneyStorageMgr::budget(const QString& _id) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); return d->m_budgetList[_id]; } -void MyMoneySeqAccessMgr::removeBudget(const MyMoneyBudget& budget) +void MyMoneyStorageMgr::removeBudget(const MyMoneyBudget& budget) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); QMap::ConstIterator it; it = d->m_budgetList.find(budget.id()); if (it == d->m_budgetList.end()) { QString msg = "Unknown budget '" + budget.id() + '\''; throw MYMONEYEXCEPTION(msg); } d->m_budgetList.remove(budget.id()); } -void MyMoneySeqAccessMgr::addPrice(const MyMoneyPrice& price) +void MyMoneyStorageMgr::addPrice(const MyMoneyPrice& price) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); MyMoneySecurityPair pricePair(price.from(), price.to()); QMap::ConstIterator it_m; it_m = d->m_priceList.find(pricePair); MyMoneyPriceEntries entries; if (it_m != d->m_priceList.end()) { entries = (*it_m); } // entries contains the current entries for this security pair // in case it_m points to m_priceList.end() we need to create a // new entry in the priceList, otherwise we need to modify // an existing one. MyMoneyPriceEntries::ConstIterator it; it = entries.constFind(price.date()); if (it != entries.constEnd()) { if ((*it).rate(QString()) == price.rate(QString()) && (*it).source() == price.source()) // in case the information did not change, we don't do anything return; } // store new value in local copy entries[price.date()] = price; if (it_m != d->m_priceList.end()) { d->m_priceList.modify(pricePair, entries); } else { d->m_priceList.insert(pricePair, entries); } } -void MyMoneySeqAccessMgr::removePrice(const MyMoneyPrice& price) +void MyMoneyStorageMgr::removePrice(const MyMoneyPrice& price) { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); MyMoneySecurityPair pricePair(price.from(), price.to()); QMap::ConstIterator it_m; it_m = d->m_priceList.find(pricePair); MyMoneyPriceEntries entries; if (it_m != d->m_priceList.end()) { entries = (*it_m); } // store new value in local copy entries.remove(price.date()); if (entries.count() != 0) { d->m_priceList.modify(pricePair, entries); } else { d->m_priceList.remove(pricePair); } } -MyMoneyPriceList MyMoneySeqAccessMgr::priceList() const +MyMoneyPriceList MyMoneyStorageMgr::priceList() const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); MyMoneyPriceList list; d->m_priceList.map(list); return list; } -MyMoneyPrice MyMoneySeqAccessMgr::price(const QString& fromId, const QString& toId, const QDate& _date, bool exactDate) const +MyMoneyPrice MyMoneyStorageMgr::price(const QString& fromId, const QString& toId, const QDate& _date, bool exactDate) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); // if the caller selected an exact entry, we can search for it using the date as the key QMap::const_iterator itm = d->m_priceList.find(qMakePair(fromId, toId)); if (itm != d->m_priceList.end()) { // if no valid date is passed, we use today's date. const QDate &date = _date.isValid() ? _date : QDate::currentDate(); const MyMoneyPriceEntries &entries = itm.value(); // regardless of the exactDate flag if the exact date is present return it's value since it's the correct value MyMoneyPriceEntries::const_iterator it = entries.find(date); if (it != entries.end()) return it.value(); // the exact date was not found look for the latest date before the requested date if the flag allows it if (!exactDate && !entries.empty()) { // if there are entries get the lower bound of the date it = entries.lowerBound(date); // since lower bound returns the first item with a larger key (we already know that key is not present) // if it's not the first item then we need to return the previous item (the map is not empty so there is one) if (it != entries.begin()) { return (--it).value(); } } } return MyMoneyPrice(); } -void MyMoneySeqAccessMgr::rebuildAccountBalances() +void MyMoneyStorageMgr::rebuildAccountBalances() { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); // reset the balance of all accounts to 0 QMap map; d->m_accountList.map(map); QMap::iterator it_a; for (it_a = map.begin(); it_a != map.end(); ++it_a) { (*it_a).setBalance(MyMoneyMoney()); } // now scan over all transactions and all splits and setup the balances foreach (const auto transaction, d->m_transactionList) { foreach (const auto split, transaction.splits()) { if (!split.shares().isZero()) { const QString& id = split.accountId(); // locate the account and if present, update data if (map.find(id) != map.end()) { map[id].adjustBalance(split); } } } } d->m_accountList = map; } -bool MyMoneySeqAccessMgr::isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const +bool MyMoneyStorageMgr::isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const { - Q_D(const MyMoneySeqAccessMgr); + Q_D(const MyMoneyStorageMgr); Q_ASSERT(skipCheck.count() == (int)Reference::Count); // We delete all references in reports when an object // is deleted, so we don't need to check here. See - // MyMoneySeqAccessMgr::removeReferences(). In case + // MyMoneyStorageMgr::removeReferences(). In case // you miss the report checks in the following lines ;) const auto& id = obj.id(); // FIXME optimize the list of objects we have to checks // with a bit of knowledge of the internal structure, we // could optimize the number of objects we check for references // Scan all engine objects for a reference if (!skipCheck.testBit((int)Reference::Transaction)) foreach (const auto it, d->m_transactionList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Account)) foreach (const auto it, d->m_accountList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Institution)) foreach (const auto it, d->m_institutionList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Payee)) foreach (const auto it, d->m_payeeList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Tag)) foreach (const auto it, d->m_tagList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Budget)) foreach (const auto it, d->m_budgetList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Schedule)) foreach (const auto it, d->m_scheduleList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Security)) foreach (const auto it, d->m_securitiesList) if (it.hasReferenceTo(id)) return true; if (!skipCheck.testBit((int)Reference::Currency)) foreach (const auto it, d->m_currencyList) if (it.hasReferenceTo(id)) return true; // within the pricelist we don't have to scan each entry. Checking the QPair // members of the MyMoneySecurityPair is enough as they are identical to the // two security ids if (!skipCheck.testBit((int)Reference::Price)) { for (auto it_pr = d->m_priceList.begin(); it_pr != d->m_priceList.end(); ++it_pr) { if ((it_pr.key().first == id) || (it_pr.key().second == id)) return true; } } return false; } -void MyMoneySeqAccessMgr::startTransaction() +void MyMoneyStorageMgr::startTransaction() { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_payeeList.startTransaction(&d->m_nextPayeeID); d->m_tagList.startTransaction(&d->m_nextTagID); d->m_institutionList.startTransaction(&d->m_nextInstitutionID); d->m_accountList.startTransaction(&d->m_nextPayeeID); d->m_transactionList.startTransaction(&d->m_nextTransactionID); d->m_transactionKeys.startTransaction(); d->m_scheduleList.startTransaction(&d->m_nextScheduleID); d->m_securitiesList.startTransaction(&d->m_nextSecurityID); d->m_currencyList.startTransaction(); d->m_reportList.startTransaction(&d->m_nextReportID); d->m_budgetList.startTransaction(&d->m_nextBudgetID); d->m_priceList.startTransaction(); d->m_onlineJobList.startTransaction(&d->m_nextOnlineJobID); } -bool MyMoneySeqAccessMgr::commitTransaction() +bool MyMoneyStorageMgr::commitTransaction() { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); bool rc = false; rc |= d->m_payeeList.commitTransaction(); rc |= d->m_tagList.commitTransaction(); rc |= d->m_institutionList.commitTransaction(); rc |= d->m_accountList.commitTransaction(); rc |= d->m_transactionList.commitTransaction(); rc |= d->m_transactionKeys.commitTransaction(); rc |= d->m_scheduleList.commitTransaction(); rc |= d->m_securitiesList.commitTransaction(); rc |= d->m_currencyList.commitTransaction(); rc |= d->m_reportList.commitTransaction(); rc |= d->m_budgetList.commitTransaction(); rc |= d->m_priceList.commitTransaction(); rc |= d->m_onlineJobList.commitTransaction(); // if there was a change, touch the whole storage object if (rc) d->touch(); return rc; } -void MyMoneySeqAccessMgr::rollbackTransaction() +void MyMoneyStorageMgr::rollbackTransaction() { - Q_D(MyMoneySeqAccessMgr); + Q_D(MyMoneyStorageMgr); d->m_payeeList.rollbackTransaction(); d->m_tagList.rollbackTransaction(); d->m_institutionList.rollbackTransaction(); d->m_accountList.rollbackTransaction(); d->m_transactionList.rollbackTransaction(); d->m_transactionKeys.rollbackTransaction(); d->m_scheduleList.rollbackTransaction(); d->m_securitiesList.rollbackTransaction(); d->m_currencyList.rollbackTransaction(); d->m_reportList.rollbackTransaction(); d->m_budgetList.rollbackTransaction(); d->m_priceList.rollbackTransaction(); d->m_onlineJobList.rollbackTransaction(); } - -QString MyMoneySeqAccessMgr::nextAccountID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextAccountID); - id = 'A' + id.rightJustified(ACCOUNT_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextTransactionID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextTransactionID); - id = 'T' + id.rightJustified(TRANSACTION_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextPayeeID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextPayeeID); - id = 'P' + id.rightJustified(PAYEE_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextTagID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextTagID); - id = 'G' + id.rightJustified(TAG_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextInstitutionID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextInstitutionID); - id = 'I' + id.rightJustified(INSTITUTION_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextScheduleID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextScheduleID); - id = "SCH" + id.rightJustified(SCHEDULE_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextSecurityID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextSecurityID); - id = 'E' + id.rightJustified(SECURITY_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextReportID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextReportID); - id = 'R' + id.rightJustified(REPORT_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextBudgetID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextBudgetID); - id = 'B' + id.rightJustified(BUDGET_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextOnlineJobID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextOnlineJobID); - id = 'O' + id.rightJustified(ONLINE_JOB_ID_SIZE, '0'); - return id; -} - -QString MyMoneySeqAccessMgr::nextCostCenterID() -{ - Q_D(MyMoneySeqAccessMgr); - QString id; - id.setNum(++d->m_nextCostCenterID); - id = 'C' + id.rightJustified(COSTCENTER_ID_SIZE, '0'); - return id; -} - -#undef TRY -#undef CATCH -#undef PASS diff --git a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.h b/kmymoney/mymoney/storage/mymoneystoragemgr.h similarity index 71% rename from kmymoney/mymoney/storage/mymoneyseqaccessmgr.h rename to kmymoney/mymoney/storage/mymoneystoragemgr.h index 1fcabf7b3..ab446cf9b 100644 --- a/kmymoney/mymoney/storage/mymoneyseqaccessmgr.h +++ b/kmymoney/mymoney/storage/mymoneystoragemgr.h @@ -1,1126 +1,1045 @@ /*************************************************************************** - mymoneyseqaccessmgr.h - description + mymoneystoragemgr.h - description ------------------- begin : Sun May 5 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ -#ifndef MYMONEYSEQACCESSMGR_H -#define MYMONEYSEQACCESSMGR_H +#ifndef MYMONEYSTORAGEMGR_H +#define MYMONEYSTORAGEMGR_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // Project Includes -#include "imymoneystorage.h" -#include "imymoneyserialize.h" #include "mymoneykeyvaluecontainer.h" +class QUrl; +class QString; +class QStringList; +class QDate; +class QBitArray; + +class MyMoneyObject; +class MyMoneyMoney; +class MyMoneyInstitution; +class MyMoneyAccount; +class MyMoneySecurity; +class MyMoneyPayee; +class MyMoneyTag; +class MyMoneyPrice; +class MyMoneyReport; +class MyMoneySchedule; +class MyMoneyBudget; +class MyMoneySplit; +class MyMoneyTransaction; +class MyMoneyTransactionFilter; +class MyMoneyCostCenter; +class onlineJob; +class MyMoneyStorageSql; + +template class QMap; +template class QList; +template struct QPair; + +typedef QPair MyMoneySecurityPair; +typedef QMap MyMoneyPriceEntries; +typedef QMap MyMoneyPriceList; + +namespace eMyMoney { namespace Schedule { enum class Type; } } +namespace eMyMoney { namespace Schedule { enum class Occurrence; } } +namespace eMyMoney { namespace Schedule { enum class PaymentType; } } + /** * @author Thomas Baumgart * @author Łukasz Wojniłowicz */ /** - * The MyMoneySeqAccessMgr class represents the storage engine for sequential + * The MyMoneyStorageMgr class represents the storage engine for sequential * files. The actual file type and it's internal storage format (e.g. binary - * or XML) is not important and handled through the IMyMoneySerialize() interface. + * or XML) is not important. * - * The MyMoneySeqAccessMgr must be loaded by an application using the - * IMyMoneySerialize() interface and can then be accessed through the - * IMyMoneyStorage() interface. All data is loaded into memory, modified + * The MyMoneyStorageMgr must be loaded by an application using loadAccounts() + * method and etc. and can then be accessed through the + * account() method and etc.. All data is loaded into memory, modified * and kept there. It is the subject of an outside object to store the - * modified data in a persistant storage area using the IMyMoneySerialize() - * interface. As indication, if data has been changed, the retrun value + * modified data in a persistant storage area using the accountList() method and etc. + * As indication, if data has been changed, the retrun value * of the method dirty() can be used. */ -class MyMoneySeqAccessMgrPrivate; -class MyMoneySeqAccessMgr : public IMyMoneyStorage, public IMyMoneySerialize, - public MyMoneyKeyValueContainer +class MyMoneyStorageMgrPrivate; +class MyMoneyStorageMgr : public MyMoneyKeyValueContainer { - Q_DISABLE_COPY(MyMoneySeqAccessMgr) + Q_DISABLE_COPY(MyMoneyStorageMgr) KMM_MYMONEY_UNIT_TESTABLE + friend class MyMoneyStorageDump; public: - - MyMoneySeqAccessMgr(); - ~MyMoneySeqAccessMgr(); + MyMoneyStorageMgr(); + ~MyMoneyStorageMgr(); // general get functions - MyMoneyPayee user() const override; - QDate creationDate() const override; - QDate lastModificationDate() const override; - uint currentFixVersion() const override; - uint fileFixVersion() const override; + MyMoneyPayee user() const; + QDate creationDate() const; + QDate lastModificationDate() const; + uint currentFixVersion() const; + uint fileFixVersion() const; // general set functions - void setUser(const MyMoneyPayee& user) override; - void setCreationDate(const QDate& val) override; - void setLastModificationDate(const QDate& val) override; - void setFileFixVersion(uint v) override; - - /** - * This method is used to get a SQL reader for subsequent database access - */ - QExplicitlySharedDataPointer connectToDatabase(const QUrl &url) override; - /** - * This method is used when a database file is open, and the data is to - * be saved in a different file or format. It will ensure that all data - * from the database is available in memory to enable it to be written. - */ - void fillStorage() override; - - /** - * This method is used to duplicate the MyMoneySeqAccessMgr object and return - * a pointer to the newly created copy. The caller of this method is the - * new owner of the object and must destroy it. - */ -// MyMoneySeqAccessMgr const * duplicate() override; + void setUser(const MyMoneyPayee& user); + void setCreationDate(const QDate& val); + void setLastModificationDate(const QDate& val); + void setFileFixVersion(uint v); /** * Returns the account addressed by it's id. * * @param id id of the account to locate. * @return reference to MyMoneyAccount object. An exception is thrown * if the id is unknown */ - MyMoneyAccount account(const QString& id) const override; + MyMoneyAccount account(const QString& id) const; /** * This method is used to check whether a given * account id references one of the standard accounts or not. * * @param id account id * @return true if account-id is one of the standards, false otherwise */ - bool isStandardAccount(const QString& id) const override; + bool isStandardAccount(const QString& id) const; /** * This method is used to set the name for the specified standard account * within the storage area. An exception will be thrown, if an error * occurs * * @param id QString reference to one of the standard accounts. Possible * values are: * * @li stdAccNames[stdAccLiability] * @li stdAccNames[stdAccAsset] * @li stdAccNames[stdAccExpense] * @li stdAccNames[stdAccIncome] * @li stdAccNames[stdAccEquity] * * @param name QString reference to the name to be set * */ - void setAccountName(const QString& id, const QString& name) override; + void setAccountName(const QString& id, const QString& name); /** * This method is used to create a new account * * An exception will be thrown upon error conditions. * * @param account MyMoneyAccount filled with data */ - void addAccount(MyMoneyAccount& account) override; + void addAccount(MyMoneyAccount& account); /** * This method is used to create a new payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ - void addPayee(MyMoneyPayee& payee) override; + void addPayee(MyMoneyPayee& payee); /** * Create now onlineJob */ - void addOnlineJob(onlineJob& job) override; + void addOnlineJob(onlineJob& job); /** * This method is used to retrieve information about a payee * An exception will be thrown upon error conditions. * * @param id QString reference to id of payee * * @return MyMoneyPayee object of payee */ - MyMoneyPayee payee(const QString& id) const override; + MyMoneyPayee payee(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a payee/receiver. * An exception will be thrown upon error conditions. * * @param payee QString reference to name of payee * * @return MyMoneyPayee reference to object of payee */ - MyMoneyPayee payeeByName(const QString& payee) const override; + MyMoneyPayee payeeByName(const QString& payee) const; /** * This method is used to modify an existing payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ - void modifyPayee(const MyMoneyPayee& payee) override; + void modifyPayee(const MyMoneyPayee& payee); /** * This method is used to remove an existing payee * * An exception will be thrown upon error conditions * * @param payee MyMoneyPayee reference to payee information */ - void removePayee(const MyMoneyPayee& payee) override; + void removePayee(const MyMoneyPayee& payee); /** * This method returns a list of the payees * inside a MyMoneyStorage object * * @return QList containing the payee information */ - QList payeeList() const override; + QList payeeList() const; /** * This method is used to create a new tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ - void addTag(MyMoneyTag& tag) override; + void addTag(MyMoneyTag& tag); /** * This method is used to retrieve information about a tag * An exception will be thrown upon error conditions. * * @param id QString reference to id of tag * * @return MyMoneyTag object of tag */ - MyMoneyTag tag(const QString& id) const override; + MyMoneyTag tag(const QString& id) const; /** * This method is used to retrieve the id to a corresponding * name of a tag. * An exception will be thrown upon error conditions. * * @param tag QString reference to name of tag * * @return MyMoneyTag reference to object of tag */ - MyMoneyTag tagByName(const QString& tag) const override; + MyMoneyTag tagByName(const QString& tag) const; /** * This method is used to modify an existing tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ - void modifyTag(const MyMoneyTag& tag) override; + void modifyTag(const MyMoneyTag& tag); /** * This method is used to remove an existing tag * * An exception will be thrown upon error conditions * * @param tag MyMoneyTag reference to tag information */ - void removeTag(const MyMoneyTag& tag) override; + void removeTag(const MyMoneyTag& tag); /** * This method returns a list of the tags * inside a MyMoneyStorage object * * @return QList containing the tag information */ - QList tagList() const override; + QList tagList() const; /** * This method is used to add one account as sub-ordinate to another * (parent) account. The objects passed as arguments will be modified * accordingly. * * @param parent parent account the account should be added to * @param account the account to be added */ - void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account) override; + void addAccount(MyMoneyAccount& parent, MyMoneyAccount& account); /** * Adds an institution to the storage. A * respective institution-ID will be generated within this record. * The ID is stored as QString in the object passed as argument. * An exception will be thrown upon error conditions. * * @param institution The complete institution information in a * MyMoneyInstitution object */ - void addInstitution(MyMoneyInstitution& institution) override; + void addInstitution(MyMoneyInstitution& institution); /** * Adds a transaction to the file-global transaction pool. A respective * transaction-ID will be generated within this record. The ID is stored * as QString in the transaction object. The accounts of the referenced splits * will be updated to have a reference to the transaction just added. * * @param transaction reference to the transaction * @param skipAccountUpdate if set, the transaction lists of the accounts * referenced in the splits are not updated. This is used for * bulk loading a lot of transactions but not during normal operation */ - void addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate = false) override; + void addTransaction(MyMoneyTransaction& transaction, bool skipAccountUpdate = false); /** * Modifies an already existing account in the file global account pool. * * An exception will be thrown upon error conditions. * * @param account reference to the new account information * @param skipCheck if @p true, skips the built in consistency check for * the object to be updated. Do not set this parameter * to true. This is only used for the MyMoneyFile::consistencyCheck() * procedure to be able to reload accounts. The default * setting of this parameter is @p false. */ - void modifyAccount(const MyMoneyAccount& account, bool skipCheck = false) override; + void modifyAccount(const MyMoneyAccount& account, bool skipCheck = false); /** * Modifies an already existing institution in the file global * institution pool. * * An exception will be thrown upon error conditions. * * @param institution The complete new institution information */ - void modifyInstitution(const MyMoneyInstitution& institution) override; + void modifyInstitution(const MyMoneyInstitution& institution); /** * This method is used to update a specific transaction in the * transaction pool of the MyMoneyFile object * * An exception will be thrown upon error conditions. * * @param transaction reference to transaction to be changed */ - void modifyTransaction(const MyMoneyTransaction& transaction) override; + void modifyTransaction(const MyMoneyTransaction& transaction); /** @todo implement */ - void modifyOnlineJob(const onlineJob& job) override; + void modifyOnlineJob(const onlineJob& job); /** * This method re-parents an existing account * * An exception will be thrown upon error conditions. * * @param account MyMoneyAccount reference to account to be re-parented * @param parent MyMoneyAccount reference to new parent account */ - void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent) override; + void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent); /** * This method is used to remove a transaction from the transaction * pool (journal). * * @param transaction const reference to transaction to be deleted */ - void removeTransaction(const MyMoneyTransaction& transaction) override; + void removeTransaction(const MyMoneyTransaction& transaction); /** * Deletes an existing account from the file global account pool * This method only allows to remove accounts that are not * referenced by any split. Use moveSplits() to move splits * to another account. An exception is thrown in case of a * problem. * * @param account reference to the account to be deleted. */ - void removeAccount(const MyMoneyAccount& account) override; + void removeAccount(const MyMoneyAccount& account); /** * Deletes an existing institution from the file global institution pool * Also modifies the accounts that reference this institution as * their institution. * * @param institution institution to be deleted. */ - void removeInstitution(const MyMoneyInstitution& institution) override; + void removeInstitution(const MyMoneyInstitution& institution); - onlineJob getOnlineJob(const QString& id) const override; + onlineJob getOnlineJob(const QString& id) const; /** @todo implement */ - ulong onlineJobId() const override; + ulong onlineJobId() const; - void removeOnlineJob(const onlineJob &) override; + void removeOnlineJob(const onlineJob &); /** * This method is used to extract a transaction from the file global * transaction pool through an id. In case of an invalid id, an * exception will be thrown. * * @param id id of transaction as QString. * @return reference to the requested transaction */ - MyMoneyTransaction transaction(const QString& id) const override; + MyMoneyTransaction transaction(const QString& id) const; /** * This method is used to extract a transaction from the file global * transaction pool through an index into an account. * * @param account id of the account as QString * @param idx number of transaction in this account * @return reference to MyMoneyTransaction object */ - MyMoneyTransaction transaction(const QString& account, const int idx) const override; + MyMoneyTransaction transaction(const QString& account, const int idx) const; /** * This method is used to determince, if the account with the * given ID is referenced by any split in m_transactionList. * * @param id id of the account to be checked for * @return true if account is referenced, false otherwise */ - bool hasActiveSplits(const QString& id) const override; + bool hasActiveSplits(const QString& id) const; /** * This method is used to return the actual balance of an account * without it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date * @return balance of the account as MyMoneyMoney object */ - MyMoneyMoney balance(const QString& id, const QDate& date) const override; + MyMoneyMoney balance(const QString& id, const QDate& date) const; /** * This method is used to return the actual balance of an account * including it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date * @return balance of the account as MyMoneyMoney object */ - MyMoneyMoney totalBalance(const QString& id, const QDate& date) const override; + MyMoneyMoney totalBalance(const QString& id, const QDate& date) const; /** * Returns the institution of a given ID * * @param id id of the institution to locate * @return MyMoneyInstitution object filled with data. If the institution * could not be found, an exception will be thrown */ - MyMoneyInstitution institution(const QString& id) const override; + MyMoneyInstitution institution(const QString& id) const; /** * This method returns an indicator if the storage object has been * changed after it has last been saved to permanent storage. * * @return true if changed, false if not */ - bool dirty() const override; + bool dirty() const; /** * This method can be used by an external object to force the * storage object to be dirty. This is used e.g. when an upload * to an external destination failed but the previous storage * to a local disk was ok. */ - void setDirty() override; + void setDirty(); /** * This method returns a list of the institutions * inside a MyMoneyFile object * * @return QMap containing the institution information */ - QList institutionList() const override; + QList institutionList() const; /** * This method returns a list of accounts inside the storage object. * * @param list reference to QList receiving the account objects * * @note The standard accounts will not be returned */ - void accountList(QList& list) const override; + void accountList(QList& list) const; /** * This method is used to pull a list of transactions from the file * global transaction pool. It returns all those transactions * that match the filter passed as argument. If the filter is empty, * the whole journal will be returned. * The list returned is sorted according to the transactions posting date. * If more than one transaction exists for the same date, the order among * them is undefined. * * The @p list will be cleared by this method. * * @param list reference to list * @param filter MyMoneyTransactionFilter object with the match criteria * * @return set of transactions in form of a QList */ - void transactionList(QList& list, MyMoneyTransactionFilter& filter) const override; + void transactionList(QList& list, MyMoneyTransactionFilter& filter) const; /** * This method is used to pull a list of transactions from the file * global transaction pool. It returns all those transactions * that match the filter passed as argument. If the filter is empty, * the whole journal will be returned. * The list returned is sorted according to the transactions posting date. * If more than one transaction exists for the same date, the order among * them is undefined. * * The @p list will be cleared by this method. * * @param list reference to list * @param filter MyMoneyTransactionFilter object with the match criteria * * @return set of transactions in form of a QList > */ - void transactionList(QList< QPair >& list, MyMoneyTransactionFilter& filter) const override; + void transactionList(QList< QPair >& list, MyMoneyTransactionFilter& filter) const; /** * Compatibility interface for the previous method. */ - QList transactionList(MyMoneyTransactionFilter& filter) const override; + QList transactionList(MyMoneyTransactionFilter& filter) const; /** * @brief Return all onlineJobs */ - QList onlineJobList() const override; + QList onlineJobList() const; /** * @brief Return all cost center objects */ - QList< MyMoneyCostCenter > costCenterList() const override; + QList< MyMoneyCostCenter > costCenterList() const; /** * @brief Return cost center object by id */ - MyMoneyCostCenter costCenter(const QString& id) const override; + MyMoneyCostCenter costCenter(const QString& id) const; /** * This method returns whether a given transaction is already in memory, to avoid * reloading it from the database */ - bool isDuplicateTransaction(const QString& id) const override; + bool isDuplicateTransaction(const QString& id) const; /** * This method returns the number of transactions currently known to file * in the range 0..MAXUINT * * @param account QString reference to account id. If account is empty + all transactions (the journal) will be counted. If account * is not empty it returns the number of transactions * that have splits in this account. * * @return number of transactions in journal/account */ - uint transactionCount(const QString& account) const override; + uint transactionCount(const QString& account) const; - QMap transactionCountMap() const override; + QMap transactionCountMap() const; /** * This method returns the number of institutions currently known to file * in the range 0..MAXUINT * * @return number of institutions known to file */ - uint institutionCount() const override; + uint institutionCount() const; /** * This method returns the number of accounts currently known to file * in the range 0..MAXUINT * * @return number of accounts currently known inside a MyMoneyFile object */ - uint accountCount() const override; + uint accountCount() const; /** * This method is used to return the standard liability account * @return MyMoneyAccount liability account(group) */ - MyMoneyAccount liability() const override; + MyMoneyAccount liability() const; /** * This method is used to return the standard asset account * @return MyMoneyAccount asset account(group) */ - MyMoneyAccount asset() const override; + MyMoneyAccount asset() const; /** * This method is used to return the standard expense account * @return MyMoneyAccount expense account(group) */ - MyMoneyAccount expense() const override; + MyMoneyAccount expense() const; /** * This method is used to return the standard income account * @return MyMoneyAccount income account(group) */ - MyMoneyAccount income() const override; + MyMoneyAccount income() const; /** * This method is used to return the standard equity account * @return MyMoneyAccount equity account(group) */ - MyMoneyAccount equity() const override; - - void loadAccounts(const QMap& acc) override; - void loadTransactions(const QMap& map) override; - void loadInstitutions(const QMap& map) override; - void loadPayees(const QMap& map) override; - void loadTags(const QMap& map) override; - void loadSchedules(const QMap& map) override; - void loadSecurities(const QMap& map) override; - void loadCurrencies(const QMap& map) override; - void loadPrices(const MyMoneyPriceList& list) override; - void loadOnlineJobs(const QMap& onlineJobs) override; - void loadCostCenters(const QMap& costCenters) override; - - void loadAccountId(ulong id) override; - void loadTransactionId(ulong id) override; - void loadPayeeId(ulong id) override; - void loadTagId(ulong id) override; - void loadInstitutionId(ulong id) override; - void loadScheduleId(ulong id) override; - void loadSecurityId(ulong id) override; - void loadReportId(ulong id) override; - void loadBudgetId(ulong id) override; - void loadOnlineJobId(ulong id) override; - void loadCostCenterId(ulong id) override; - - ulong accountId() const override; - ulong transactionId() const override; - ulong payeeId() const override; - ulong tagId() const override; - ulong institutionId() const override; - ulong scheduleId() const override; - ulong securityId() const override; - ulong reportId() const override; - ulong budgetId() const override; - ulong costCenterId() const override; - - /** - * This method is used to extract a value from - * KeyValueContainer. For details see MyMoneyKeyValueContainer::value(). - * - * @param key const reference to QString containing the key - * @return QString containing the value - */ - QString value(const QString& key) const override; + MyMoneyAccount equity() const; + + void loadAccounts(const QMap& acc); + void loadTransactions(const QMap& map); + void loadInstitutions(const QMap& map); + void loadPayees(const QMap& map); + void loadTags(const QMap& map); + void loadSchedules(const QMap& map); + void loadSecurities(const QMap& map); + void loadCurrencies(const QMap& map); + void loadPrices(const MyMoneyPriceList& list); + void loadOnlineJobs(const QMap& onlineJobs); + void loadCostCenters(const QMap& costCenters); /** * This method is used to set a value in the * KeyValueContainer. For details see MyMoneyKeyValueContainer::setValue(). * * @param key const reference to QString containing the key * @param val const reference to QString containing the value */ - void setValue(const QString& key, const QString& val) override; + void setValue(const QString& key, const QString& val); /** * This method is used to delete a key-value-pair from the * KeyValueContainer identified by the parameter * @p key. For details see MyMoneyKeyValueContainer::deletePair(). * * @param key const reference to QString containing the key */ - void deletePair(const QString& key) override; + void deletePair(const QString& key); - // documented in IMyMoneySerialize base class - QMap pairs() const override; - - // documented in IMyMoneySerialize base class - void setPairs(const QMap& list) override; + /** + * This method is used to initially store a set of key/value pairs + * in the container. It is meant to be used for loading functionality + * from permanent storage. See MyMoneyKeyValueContainer::setPairs() + * for details + * + * @param list const QMap containing the set of + * key/value pairs to be loaded into the container. + * + * @note All existing key/value pairs in the container will be deleted. + */ + void setPairs(const QMap& list); /** * This method is used to add a scheduled transaction to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param sched reference to the MyMoneySchedule object */ - void addSchedule(MyMoneySchedule& sched) override; + void addSchedule(MyMoneySchedule& sched); /** * This method is used to modify an existing MyMoneySchedule * object. Therefor, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ - void modifySchedule(const MyMoneySchedule& sched) override; + void modifySchedule(const MyMoneySchedule& sched); /** * This method is used to remove an existing MyMoneySchedule object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param sched const reference to the MyMoneySchedule object to be updated */ - void removeSchedule(const MyMoneySchedule& sched) override; + void removeSchedule(const MyMoneySchedule& sched); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ - MyMoneySchedule schedule(const QString& id) const override; + MyMoneySchedule schedule(const QString& id) const; /** * This method is used to create a new security object. The ID will be created * automatically. The object passed with the parameter @p security is modified * to contain the assigned id. * * An exception will be thrown upon error conditions. * * @param security MyMoneySecurity filled with data */ - void addSecurity(MyMoneySecurity& security) override; + void addSecurity(MyMoneySecurity& security); /** * This method is used to modify an existing MyMoneySchedule * object. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be updated */ - void modifySecurity(const MyMoneySecurity& security) override; + void modifySecurity(const MyMoneySecurity& security); /** * This method is used to remove an existing MyMoneySecurity object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param security reference to the MyMoneySecurity object to be removed */ - void removeSecurity(const MyMoneySecurity& security) override; + void removeSecurity(const MyMoneySecurity& security); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ - MyMoneySecurity security(const QString& id) const override; + MyMoneySecurity security(const QString& id) const; /** * This method returns a list of security objects that the engine has * knowledge of. */ - QList securityList() const override; + QList securityList() const; /** * This method is used to add a new currency object to the engine. * The ID of the object is the trading symbol, so there is no need for an additional * ID since the symbol is guaranteed to be unique. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneyCurrency object */ - void addCurrency(const MyMoneySecurity& currency) override; + void addCurrency(const MyMoneySecurity& currency); /** * This method is used to modify an existing MyMoneyCurrency * object. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneyCurrency object */ - void modifyCurrency(const MyMoneySecurity& currency) override; + void modifyCurrency(const MyMoneySecurity& currency); /** * This method is used to remove an existing MyMoneyCurrency object * from the engine. * * An exception will be thrown upon erroneous situations. * * @param currency reference to the MyMoneyCurrency object */ - void removeCurrency(const MyMoneySecurity& currency) override; + void removeCurrency(const MyMoneySecurity& currency); /** * This method is used to retrieve a single MyMoneySchedule object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneySchedule object * @return MyMoneySchedule object */ - MyMoneySecurity currency(const QString& id) const override; + MyMoneySecurity currency(const QString& id) const; /** * This method is used to retrieve the list of all currencies * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyCurrency objects. */ - QList currencyList() const override; + QList currencyList() const; /** * This method is used to extract a list of scheduled transactions * according to the filter criteria passed as arguments. * * @param accountId only search for scheduled transactions that reference * accound @p accountId. If accountId is the empty string, * this filter is off. Default is @p QString(). * @param type only schedules of type @p type are searched for. * See eMyMoney::Schedule::Type for details. * Default is eMyMoney::Schedule::Type::Any * @param occurrence only schedules of occurrence type @p occurrence are searched for. * See eMyMoney::Schedule::Occurrence for details. * Default is eMyMoney::Schedule::Occurrence::Any * @param paymentType only schedules of payment method @p paymentType * are searched for. * See eMyMoney::Schedule::PaymentType for details. * Default is eMyMoney::Schedule::PaymentType::Any * @param startDate only schedules with payment dates after @p startDate * are searched for. Default is all dates (QDate()). * @param endDate only schedules with payment dates ending prior to @p endDate * are searched for. Default is all dates (QDate()). * @param overdue if true, only those schedules that are overdue are * searched for. Default is false (all schedules will be returned). * * @return QList list of schedule objects. */ QList scheduleList(const QString& accountId, eMyMoney::Schedule::Type type, eMyMoney::Schedule::Occurrence occurrence, eMyMoney::Schedule::PaymentType paymentType, const QDate& startDate, const QDate& endDate, - bool overdue) const override; + bool overdue) const; QList scheduleListEx(int scheduleTypes, int scheduleOcurrences, int schedulePaymentTypes, QDate startDate, - const QStringList& accounts) const override; + const QStringList& accounts) const; /** * This method is used to retrieve the list of all reports * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyReport objects. */ - QList reportList() const override; + QList reportList() const; /** * This method is used to add a new report to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param report reference to the MyMoneyReport object */ - void addReport(MyMoneyReport& report) override; + void addReport(MyMoneyReport& report); /** * This method is used to load a set of reports into the engine. This is * used when loading from storage, and an ID is already known. * * An exception will be thrown upon erroneous situations. * * @param reports reference to the map of MyMoneyReport objects */ - void loadReports(const QMap& reports) override; + void loadReports(const QMap& reports); /** * This method is used to modify an existing MyMoneyReport * object. Therefor, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param report const reference to the MyMoneyReport object to be updated */ - void modifyReport(const MyMoneyReport& report) override; + void modifyReport(const MyMoneyReport& report); /** * This method returns the number of reports currently known to file * in the range 0..MAXUINT * * @return number of reports known to file */ - uint countReports() const override; + uint countReports() const; /** * This method is used to retrieve a single MyMoneyReport object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyReport object * @return MyMoneyReport object */ - MyMoneyReport report(const QString& id) const override; + MyMoneyReport report(const QString& id) const; /** * This method is used to remove an existing MyMoneyReport object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param report const reference to the MyMoneyReport object to be updated */ - void removeReport(const MyMoneyReport& report) override; + void removeReport(const MyMoneyReport& report); /** * This method is used to retrieve the list of all budgets * known to the engine. * * An exception will be thrown upon erroneous situations. * * @return QList of all MyMoneyBudget objects. */ - QList budgetList() const override; + QList budgetList() const; /** * This method is used to add a new budget to the engine. * It must be sure, that the id of the object is not filled. When the * method returns to the caller, the id will be filled with the * newly created object id value. * * An exception will be thrown upon erroneous situations. * * @param budget reference to the MyMoneyBudget object */ - void addBudget(MyMoneyBudget& budget) override; + void addBudget(MyMoneyBudget& budget); /** * This method is used to load a set of budgets into the engine. This is * used when loading from storage, and an ID is already known. * * An exception will be thrown upon erroneous situations. * * @param budgets reference to the map of MyMoneyBudget object */ - void loadBudgets(const QMap& budgets) override; + void loadBudgets(const QMap& budgets); /** * This method is used to retrieve the id to a corresponding * name of a budget * An exception will be thrown upon error conditions. * * @param budget QString reference to name of budget * * @return MyMoneyBudget reference to object of budget */ - MyMoneyBudget budgetByName(const QString& budget) const override; + MyMoneyBudget budgetByName(const QString& budget) const; /** * This method is used to modify an existing MyMoneyBudget * object. Therefore, the id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param budget const reference to the MyMoneyBudget object to be updated */ - void modifyBudget(const MyMoneyBudget& budget) override; + void modifyBudget(const MyMoneyBudget& budget); /** * This method returns the number of budgets currently known to file * in the range 0..MAXUINT * * @return number of budgets known to file */ - uint countBudgets() const override; + uint countBudgets() const; /** * This method is used to retrieve a single MyMoneyBudget object. * The id of the object must be supplied in the parameter @p id. * * An exception will be thrown upon erroneous situations. * * @param id QString containing the id of the MyMoneyBudget object * @return MyMoneyBudget object */ - MyMoneyBudget budget(const QString& id) const override; + MyMoneyBudget budget(const QString& id) const; /** * This method is used to remove an existing MyMoneyBudget object * from the engine. The id attribute of the object must be set. * * An exception will be thrown upon erroneous situations. * * @param budget const reference to the MyMoneyBudget object to be updated */ - void removeBudget(const MyMoneyBudget& budget) override; + void removeBudget(const MyMoneyBudget& budget); /** * This method adds/replaces a price to/from the price list */ - void addPrice(const MyMoneyPrice& price) override; + void addPrice(const MyMoneyPrice& price); /** * This method removes a price from the price list */ - void removePrice(const MyMoneyPrice& price) override; + void removePrice(const MyMoneyPrice& price); /** * This method retrieves a price from the price list. * If @p date is inValid, QDate::currentDate() is assumed. */ - MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& _date, bool exactDate) const override; + MyMoneyPrice price(const QString& fromId, const QString& toId, const QDate& _date, bool exactDate) const; /** * This method returns a list of all price entries. */ - MyMoneyPriceList priceList() const override; + MyMoneyPriceList priceList() const; /** * This method checks, if the given @p object is referenced * by another engine object. * * @param obj const reference to object to be checked * @param skipCheck QBitArray with eStorage::Reference bits set for which * the check should be skipped * * @retval false @p object is not referenced * @retval true @p institution is referenced */ - bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const override; + bool isReferenced(const MyMoneyObject& obj, const QBitArray& skipCheck) const; /** * This method recalculates the balances of all accounts * based on the transactions stored in the engine. */ - void rebuildAccountBalances() override; - - void startTransaction() override; - bool commitTransaction() override; - void rollbackTransaction() override; + void rebuildAccountBalances(); -protected: - /** - * The member variable m_nextAccountID keeps the number that will be - * assigned to the next institution created. It is maintained by - * nextAccountID(). - */ - QString nextAccountID() override; - - /** - * The member variable m_nextTransactionID keeps the number that will be - * assigned to the next transaction created. It is maintained by - * nextTransactionID(). - */ - QString nextTransactionID() override; - - /** - * The member variable m_nextPayeeID keeps the number that will be - * assigned to the next payee created. It is maintained by - * nextPayeeID() - */ - QString nextPayeeID() override; - - /** - * The member variable m_nextTagID keeps the number that will be - * assigned to the next tag created. It is maintained by - * nextTagID() - */ - QString nextTagID() override; - - /** - * The member variable m_nextInstitutionID keeps the number that will be - * assigned to the next institution created. It is maintained by - * nextInstitutionID(). - */ - QString nextInstitutionID() override; - - /** - * The member variable m_nextScheduleID keeps the number that will be - * assigned to the next schedule created. It is maintained by - * nextScheduleID() - */ - QString nextScheduleID() override; - - /** - * The member variable m_nextSecurityID keeps the number that will be - * assigned to the next security object created. It is maintained by - * nextSecurityID() - */ - QString nextSecurityID() override; - - QString nextReportID() override; - - /** - * The member variable m_nextBudgetID keeps the number that will be - * assigned to the next budget object created. It is maintained by - * nextBudgetID() - */ - QString nextBudgetID() override; - - /** - * This member variable keeps the number that will be assigned to the - * next onlineJob object created. It is maintained by nextOnlineJobID() - */ - QString nextOnlineJobID() override; - - /** - * This member variable keeps the number that will be assigned to the - * next cost center object created. It is maintained by nextCostCenterID() - */ - QString nextCostCenterID() override; - -private: - MyMoneySeqAccessMgrPrivate* const d_ptr; - Q_DECLARE_PRIVATE_D(MyMoneySeqAccessMgr::d_ptr, MyMoneySeqAccessMgr) + void startTransaction(); + bool commitTransaction(); + void rollbackTransaction(); /** * This method will close a database and log the use roff */ - void close() override; + void close(); + +private: + MyMoneyStorageMgrPrivate * const d_ptr; + Q_DECLARE_PRIVATE(MyMoneyStorageMgr) }; #endif diff --git a/kmymoney/mymoney/storage/mymoneyseqaccessmgr_p.h b/kmymoney/mymoney/storage/mymoneystoragemgr_p.h similarity index 68% rename from kmymoney/mymoney/storage/mymoneyseqaccessmgr_p.h rename to kmymoney/mymoney/storage/mymoneystoragemgr_p.h index 7d33a0d12..fd9035e14 100644 --- a/kmymoney/mymoney/storage/mymoneyseqaccessmgr_p.h +++ b/kmymoney/mymoney/storage/mymoneystoragemgr_p.h @@ -1,414 +1,609 @@ /*************************************************************************** - mymoneyseqaccessmgr.cpp + mymoneystoragemgr.cpp ------------------- begin : Sun May 5 2002 copyright : (C) 2000-2002 by Michael Edwardes 2002 Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ -#ifndef MYMONEYSEQACCESSMGR_P_H -#define MYMONEYSEQACCESSMGR_P_H +#ifndef MYMONEYSTORAGEMGR_P_H +#define MYMONEYSTORAGEMGR_P_H -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" -#include "mymoneystoragesql.h" #include "storageenums.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneytag.h" #include "mymoneypayee.h" #include "mymoneybudget.h" #include "mymoneyschedule.h" #include "mymoneyreport.h" #include "mymoneymoney.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneycostcenter.h" #include "mymoneymap.h" +#include "onlinejob.h" #include "mymoneyenums.h" +#include "mymoneystoragenames.h" +using namespace MyMoneyStandardAccounts; using namespace eStorage; -class MyMoneySeqAccessMgrPrivate +const int INSTITUTION_ID_SIZE = 6; +const int ACCOUNT_ID_SIZE = 6; +const int TRANSACTION_ID_SIZE = 18; +const int PAYEE_ID_SIZE = 6; +const int TAG_ID_SIZE = 6; +const int SCHEDULE_ID_SIZE = 6; +const int SECURITY_ID_SIZE = 6; +const int REPORT_ID_SIZE = 6; +const int BUDGET_ID_SIZE = 6; +const int ONLINE_JOB_ID_SIZE = 6; +const int COSTCENTER_ID_SIZE = 6; + +class MyMoneyStorageMgrPrivate { - Q_DISABLE_COPY(MyMoneySeqAccessMgrPrivate) - Q_DECLARE_PUBLIC(MyMoneySeqAccessMgr) + Q_DISABLE_COPY(MyMoneyStorageMgrPrivate) + Q_DECLARE_PUBLIC(MyMoneyStorageMgr) public: - explicit MyMoneySeqAccessMgrPrivate(MyMoneySeqAccessMgr* qq) : + explicit MyMoneyStorageMgrPrivate(MyMoneyStorageMgr* qq) : q_ptr(qq), m_nextInstitutionID(0), m_nextAccountID(0), m_nextTransactionID(0), m_nextPayeeID(0), m_nextTagID(0), m_nextScheduleID(0), m_nextSecurityID(0), m_nextReportID(0), m_nextBudgetID(0), m_nextOnlineJobID(0), m_nextCostCenterID(0), m_dirty(false), m_creationDate(QDate::currentDate()), // initialize for file fixes (see kmymoneyview.cpp) m_currentFixVersion(4), m_fileFixVersion(0), // default value if no fix-version in file m_transactionListFull(false) { } - ~MyMoneySeqAccessMgrPrivate() + ~MyMoneyStorageMgrPrivate() { } + void init() + { + // setup standard accounts + MyMoneyAccount acc_l; + acc_l.setAccountType(eMyMoney::Account::Type::Liability); + acc_l.setName("Liability"); + MyMoneyAccount liability(stdAccNames[stdAccLiability], acc_l); + + MyMoneyAccount acc_a; + acc_a.setAccountType(eMyMoney::Account::Type::Asset); + acc_a.setName("Asset"); + MyMoneyAccount asset(stdAccNames[stdAccAsset], acc_a); + + MyMoneyAccount acc_e; + acc_e.setAccountType(eMyMoney::Account::Type::Expense); + acc_e.setName("Expense"); + MyMoneyAccount expense(stdAccNames[stdAccExpense], acc_e); + + MyMoneyAccount acc_i; + acc_i.setAccountType(eMyMoney::Account::Type::Income); + acc_i.setName("Income"); + MyMoneyAccount income(stdAccNames[stdAccIncome], acc_i); + + MyMoneyAccount acc_q; + acc_q.setAccountType(eMyMoney::Account::Type::Equity); + acc_q.setName("Equity"); + MyMoneyAccount equity(stdAccNames[stdAccEquity], acc_q); + + QMap map; + map[stdAccNames[stdAccAsset]] = asset; + map[stdAccNames[stdAccLiability]] = liability; + map[stdAccNames[stdAccIncome]] = income; + map[stdAccNames[stdAccExpense]] = expense; + map[stdAccNames[stdAccEquity]] = equity; + + // load account list with initial accounts + m_accountList = map; + } + /** * This method is used to set the dirty flag and update the * date of the last modification. */ void touch() { m_dirty = true; m_lastModificationDate = QDate::currentDate(); } /** * Adjust the balance for account @a acc by the amount of shares in split @a split. * The amount is added if @a reverse is @c false, subtracted in case it is @c true. */ void adjustBalance(MyMoneyAccount& acc, const MyMoneySplit& split, bool reverse) { // in case of an investment we can't just add or subtract the // amount of the split since we don't know about stock splits. // so in the case of those stocks, we simply recalculate the balance from scratch acc.isInvest() ? acc.setBalance(calculateBalance(acc.id(), QDate())) : acc.adjustBalance(split, reverse); } /** * This method re-parents an existing account * * An exception will be thrown upon error conditions. * * @param account MyMoneyAccount reference to account to be re-parented * @param parent MyMoneyAccount reference to new parent account * @param sendNotification if true, notifications with the ids * of all modified objects are send */ void reparentAccount(MyMoneyAccount &account, MyMoneyAccount& parent, bool /* sendNotification */) { - Q_Q(MyMoneySeqAccessMgr); + Q_Q(MyMoneyStorageMgr); QMap::ConstIterator oldParent; QMap::ConstIterator newParent; QMap::ConstIterator childAccount; // verify that accounts exist. If one does not, // an exception is thrown q->account(account.id()); q->account(parent.id()); if (!account.parentAccountId().isEmpty()) { q->account(account.parentAccountId()); oldParent = m_accountList.find(account.parentAccountId()); } if (account.accountType() == eMyMoney::Account::Type::Stock && parent.accountType() != eMyMoney::Account::Type::Investment) throw MYMONEYEXCEPTION("Cannot move a stock acocunt into a non-investment account"); newParent = m_accountList.find(parent.id()); childAccount = m_accountList.find(account.id()); MyMoneyAccount acc; if (!account.parentAccountId().isEmpty()) { acc = (*oldParent); acc.removeAccountId(account.id()); m_accountList.modify(acc.id(), acc); } parent = (*newParent); parent.addAccountId(account.id()); m_accountList.modify(parent.id(), parent); account = (*childAccount); account.setParentAccountId(parent.id()); m_accountList.modify(account.id(), account); #if 0 // make sure the type is the same as the new parent. This does not work for stock and investment if (account.accountType() != eMyMoney::Account::Type::Stock && account.accountType() != eMyMoney::Account::Type::Investment) (*childAccount).setAccountType((*newParent).accountType()); #endif } /** * This method is used to calculate the actual balance of an account * without it's sub-ordinate accounts. If a @p date is presented, * the balance at the beginning of this date (not including any * transaction on this date) is returned. Otherwise all recorded * transactions are included in the balance. * * @param id id of the account in question * @param date return balance for specific date * @return balance of the account as MyMoneyMoney object */ MyMoneyMoney calculateBalance(const QString& id, const QDate& date) const { - Q_Q(const MyMoneySeqAccessMgr); + Q_Q(const MyMoneyStorageMgr); MyMoneyMoney balance; QList list; MyMoneyTransactionFilter filter; filter.setDateFilter(QDate(), date); filter.setReportAllSplits(false); q->transactionList(list, filter); for (const auto& transaction : list) { const auto splits = transaction.splits(); for (const auto& split : splits) { if (split.accountId().compare(id) != 0) continue; else if (split.action().compare(MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) == 0) balance *= split.shares(); else balance += split.shares(); } } return balance; } void removeReferences(const QString& id) { QMap::const_iterator it_r; QMap::const_iterator it_b; // remove from reports for (it_r = m_reportList.begin(); it_r != m_reportList.end(); ++it_r) { MyMoneyReport r = *it_r; r.removeReference(id); m_reportList.modify(r.id(), r); } // remove from budgets for (it_b = m_budgetList.begin(); it_b != m_budgetList.end(); ++it_b) { MyMoneyBudget b = *it_b; b.removeReference(id); m_budgetList.modify(b.id(), b); } } - MyMoneySeqAccessMgr *q_ptr; + /** + * The member variable m_nextAccountID keeps the number that will be + * assigned to the next institution created. It is maintained by + * nextAccountID(). + */ + QString nextAccountID() + { + QString id; + id.setNum(++m_nextAccountID); + id = 'A' + id.rightJustified(ACCOUNT_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextTransactionID keeps the number that will be + * assigned to the next transaction created. It is maintained by + * nextTransactionID(). + */ + QString nextTransactionID() + { + QString id; + id.setNum(++m_nextTransactionID); + id = 'T' + id.rightJustified(TRANSACTION_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextPayeeID keeps the number that will be + * assigned to the next payee created. It is maintained by + * nextPayeeID() + */ + QString nextPayeeID() + { + QString id; + id.setNum(++m_nextPayeeID); + id = 'P' + id.rightJustified(PAYEE_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextTagID keeps the number that will be + * assigned to the next tag created. It is maintained by + * nextTagID() + */ + QString nextTagID() + { + QString id; + id.setNum(++m_nextTagID); + id = 'G' + id.rightJustified(TAG_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextInstitutionID keeps the number that will be + * assigned to the next institution created. It is maintained by + * nextInstitutionID(). + */ + QString nextInstitutionID() + { + QString id; + id.setNum(++m_nextInstitutionID); + id = 'I' + id.rightJustified(INSTITUTION_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextScheduleID keeps the number that will be + * assigned to the next schedule created. It is maintained by + * nextScheduleID() + */ + QString nextScheduleID() + { + QString id; + id.setNum(++m_nextScheduleID); + id = "SCH" + id.rightJustified(SCHEDULE_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextSecurityID keeps the number that will be + * assigned to the next security object created. It is maintained by + * nextSecurityID() + */ + QString nextSecurityID() + { + QString id; + id.setNum(++m_nextSecurityID); + id = 'E' + id.rightJustified(SECURITY_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextReportID keeps the number that will be + * assigned to the next report object created. It is maintained by + * nextReportID() + */ + QString nextReportID() + { + QString id; + id.setNum(++m_nextReportID); + id = 'R' + id.rightJustified(REPORT_ID_SIZE, '0'); + return id; + } + + /** + * The member variable m_nextBudgetID keeps the number that will be + * assigned to the next budget object created. It is maintained by + * nextBudgetID() + */ + QString nextBudgetID() + { + QString id; + id.setNum(++m_nextBudgetID); + id = 'B' + id.rightJustified(BUDGET_ID_SIZE, '0'); + return id; + } + + /** + * This member variable keeps the number that will be assigned to the + * next onlineJob object created. It is maintained by nextOnlineJobID() + */ + QString nextOnlineJobID() + { + QString id; + id.setNum(++m_nextOnlineJobID); + id = 'O' + id.rightJustified(ONLINE_JOB_ID_SIZE, '0'); + return id; + } + + /** + * This member variable keeps the number that will be assigned to the + * next cost center object created. It is maintained by nextCostCenterID() + */ + QString nextCostCenterID() + { + QString id; + id.setNum(++m_nextCostCenterID); + id = 'C' + id.rightJustified(COSTCENTER_ID_SIZE, '0'); + return id; + } + + MyMoneyStorageMgr *q_ptr; + /** * This member variable keeps the User information. * @see setUser() */ MyMoneyPayee m_user; /** * The member variable m_nextInstitutionID keeps the number that will be * assigned to the next institution created. It is maintained by * nextInstitutionID(). */ ulong m_nextInstitutionID; /** * The member variable m_nextAccountID keeps the number that will be * assigned to the next institution created. It is maintained by * nextAccountID(). */ ulong m_nextAccountID; /** * The member variable m_nextTransactionID keeps the number that will be * assigned to the next transaction created. It is maintained by * nextTransactionID(). */ ulong m_nextTransactionID; /** * The member variable m_nextPayeeID keeps the number that will be * assigned to the next payee created. It is maintained by * nextPayeeID() */ ulong m_nextPayeeID; /** * The member variable m_nextTagID keeps the number that will be * assigned to the next tag created. It is maintained by * nextTagID() */ ulong m_nextTagID; /** * The member variable m_nextScheduleID keeps the number that will be * assigned to the next schedule created. It is maintained by * nextScheduleID() */ ulong m_nextScheduleID; /** * The member variable m_nextSecurityID keeps the number that will be * assigned to the next security object created. It is maintained by * nextSecurityID() */ ulong m_nextSecurityID; ulong m_nextReportID; /** * The member variable m_nextBudgetID keeps the number that will be * assigned to the next budget object created. It is maintained by * nextBudgetID() */ ulong m_nextBudgetID; /** * This member variable keeps the number that will be assigned to the * next onlineJob object created. It is maintained by nextOnlineJobID() */ ulong m_nextOnlineJobID; /** * This member variable keeps the number that will be assigned to the * next cost center object created. It is maintained by nextCostCenterID() */ ulong m_nextCostCenterID; /** * The member variable m_institutionList is the container for the * institutions known within this file. */ MyMoneyMap m_institutionList; /** * The member variable m_accountList is the container for the accounts * known within this file. */ MyMoneyMap m_accountList; /** * The member variable m_transactionList is the container for all * transactions within this file. * @see m_transactionKeys */ MyMoneyMap m_transactionList; /** * The member variable m_transactionKeys is used to convert * transaction id's into the corresponding key used in m_transactionList. * @see m_transactionList; */ MyMoneyMap m_transactionKeys; /** * A list containing all the payees that have been used */ MyMoneyMap m_payeeList; /** * A list containing all the tags that have been used */ MyMoneyMap m_tagList; /** * A list containing all the scheduled transactions */ MyMoneyMap m_scheduleList; /** * A list containing all the security information objects. Each object * can represent a stock, bond, or mutual fund. It contains a price * history that a user can add entries to. The price history will be used * to determine the cost basis for sales, as well as the source of * information for reports in a security account. */ MyMoneyMap m_securitiesList; /** * A list containing all the currency information objects. */ MyMoneyMap m_currencyList; MyMoneyMap m_reportList; /** * A list containing all the budget information objects. */ MyMoneyMap m_budgetList; MyMoneyMap m_priceList; /** * A list containing all the onlineJob information objects. */ MyMoneyMap m_onlineJobList; /** * A list containing all the cost center information objects */ MyMoneyMap m_costCenterList; /** * This member signals if the file has been modified or not */ bool m_dirty; /** - * This member variable keeps the creation date of this MyMoneySeqAccessMgr + * This member variable keeps the creation date of this MyMoneyStorageMgr * object. It is set during the constructor and can only be modified using * the stream read operator. */ QDate m_creationDate; /** * This member variable keeps the date of the last modification of - * the MyMoneySeqAccessMgr object. + * the MyMoneyStorageMgr object. */ QDate m_lastModificationDate; /** * This member variable contains the current fix level of application * data files. (see kmymoneyview.cpp) */ uint m_currentFixVersion; /** * This member variable contains the current fix level of the * presently open data file. (see kmymoneyview.cpp) */ uint m_fileFixVersion; /** * This member variable is set when all transactions have been read from the database. * This is would be probably the case when doing, for e.g., a full report, * or after some types of transaction search which cannot be easily implemented in SQL */ bool m_transactionListFull; }; #endif diff --git a/kmymoney/mymoney/storage/mymoneystoragesql.cpp b/kmymoney/mymoney/storage/mymoneystoragesql.cpp index ce4040d78..80126d599 100644 --- a/kmymoney/mymoney/storage/mymoneystoragesql.cpp +++ b/kmymoney/mymoney/storage/mymoneystoragesql.cpp @@ -1,2832 +1,2834 @@ /*************************************************************************** mymoneystoragesql.cpp --------------------- begin : 11 November 2005 copyright : (C) 2005 by Tony Bloomfield email : tonybloom@users.sourceforge.net : Fernando Vilas : Christian Dávid (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneystoragesql_p.h" // ---------------------------------------------------------------------------- // System Includes // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes //************************ Constructor/Destructor ***************************** -MyMoneyStorageSql::MyMoneyStorageSql(IMyMoneySerialize *storage, const QUrl &url) : +MyMoneyStorageSql::MyMoneyStorageSql(MyMoneyStorageMgr *storage, const QUrl &url) : QSqlDatabase(QUrlQuery(url).queryItemValue("driver")), d_ptr(new MyMoneyStorageSqlPrivate(this)) { Q_D(MyMoneyStorageSql); d->m_storage = storage; - d->m_storagePtr = dynamic_cast(d->m_storage); } MyMoneyStorageSql::~MyMoneyStorageSql() { try { close(true); } catch (const MyMoneyException& e) { qDebug() << "Caught Exception in MMStorageSql dtor: " << e.what(); } Q_D(MyMoneyStorageSql); delete d; } uint MyMoneyStorageSql::currentVersion() const { Q_D(const MyMoneyStorageSql); return (d->m_db.currentVersion()); } int MyMoneyStorageSql::open(const QUrl &url, int openMode, bool clear) { Q_D(MyMoneyStorageSql); try { int rc = 0; d->m_driver = MyMoneyDbDriver::create(QUrlQuery(url).queryItemValue("driver")); //get the input options QStringList options = QUrlQuery(url).queryItemValue("options").split(','); - d->m_loadAll = options.contains("loadAll")/*|| m_mode == 0*/; + d->m_loadAll = true || // force loading whole database into memory since unification of storages + options.contains("loadAll")/*|| m_mode == 0*/; d->m_override = options.contains("override"); // create the database connection QString dbName = url.path(); setDatabaseName(dbName); setHostName(url.host()); setUserName(url.userName()); setPassword(url.password()); if (QUrlQuery(url).queryItemValue("driver").contains("QMYSQL")) { setConnectOptions("MYSQL_OPT_RECONNECT=1"); } switch (openMode) { case QIODevice::ReadOnly: // OpenDatabase menu entry (or open last file) case QIODevice::ReadWrite: // Save menu entry with database open // this may be a sqlite file opened from the recently used list // but which no longer exists. In that case, open will work but create an empty file. // This is not what the user's after; he may accuse KMM of deleting all his data! if (d->m_driver->requiresExternalFile()) { if (!d->fileExists(dbName)) { rc = 1; break; } } if (!QSqlDatabase::open()) { d->buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening database"); rc = 1; } else { rc = d->createTables(); // check all tables are present, create if not } break; case QIODevice::WriteOnly: // SaveAs Database - if exists, must be empty, if not will create // Try to open the database. // If that fails, try to create the database, then try to open it again. d->m_newDatabase = true; if (!QSqlDatabase::open()) { if (!d->createDatabase(url)) { rc = 1; } else { if (!QSqlDatabase::open()) { d->buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening new database"); rc = 1; } else { rc = d->createTables(); } } } else { rc = d->createTables(); if (rc == 0) { if (clear) { d->clean(); } else { rc = d->isEmpty(); } } } break; default: qWarning("%s", qPrintable(QString("%1 - unknown open mode %2").arg(Q_FUNC_INFO).arg(openMode))); } if (rc != 0) return (rc); // bypass logon check if we are creating a database if (d->m_newDatabase) return(0); // check if the database is locked, if not lock it d->readFileInfo(); if (!d->m_logonUser.isEmpty() && (!d->m_override)) { d->m_error = i18n("Database apparently in use\nOpened by %1 on %2 at %3.\nOpen anyway?", d->m_logonUser, d->m_logonAt.date().toString(Qt::ISODate), d->m_logonAt.time().toString("hh.mm.ss")); qDebug("%s", qPrintable(d->m_error)); close(false); rc = -1; // retryable error } else { d->m_logonUser = url.userName() + '@' + url.host(); d->m_logonAt = QDateTime::currentDateTime(); d->writeFileInfo(); } return(rc); } catch (const QString& s) { qDebug("%s", qPrintable(s)); return (1); } } void MyMoneyStorageSql::close(bool logoff) { Q_D(MyMoneyStorageSql); if (QSqlDatabase::isOpen()) { if (logoff) { MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->m_logonUser.clear(); d->writeFileInfo(); } QSqlDatabase::close(); QSqlDatabase::removeDatabase(connectionName()); } } ulong MyMoneyStorageSql::getRecCount(const QString& table) const { Q_D(const MyMoneyStorageSql); QSqlQuery q(*const_cast (this)); q.prepare(QString("SELECT COUNT(*) FROM %1;").arg(table)); if ((!q.exec()) || (!q.next())) { // krazy:exclude=crashy d->buildError(q, Q_FUNC_INFO, "error retrieving record count"); qFatal("Error retrieving record count"); // definitely shouldn't happen } return ((ulong) q.value(0).toULongLong()); } ////////////////////////////////////////////////////////////////// bool MyMoneyStorageSql::readFile() { Q_D(MyMoneyStorageSql); d->m_displayStatus = true; try { d->readFileInfo(); d->readInstitutions(); if (d->m_loadAll) { readPayees(); } else { QList user; user.append(QString("USER")); readPayees(user); } readTags(); d->readCurrencies(); d->readSecurities(); d->readAccounts(); if (d->m_loadAll) { d->readTransactions(); } else { if (d->m_preferred.filterSet().singleFilter.accountFilter) readTransactions(d->m_preferred); } d->readSchedules(); d->readPrices(); d->readReports(); d->readBudgets(); //FIXME - ?? if (m_mode == 0) //m_storage->rebuildAccountBalances(); // this seems to be nonsense, but it clears the dirty flag // as a side-effect. d->m_storage->setLastModificationDate(d->m_storage->lastModificationDate()); // FIXME?? if (m_mode == 0) m_storage = NULL; // make sure the progress bar is not shown any longer d->signalProgress(-1, -1); d->m_displayStatus = false; //MyMoneySqlQuery::traceOn(); return true; } catch (const QString &) { return false; } + // this seems to be nonsense, but it clears the dirty flag + // as a side-effect. } // The following is called from 'SaveAsDatabase' bool MyMoneyStorageSql::writeFile() { Q_D(MyMoneyStorageSql); // initialize record counts and hi ids d->m_institutions = d->m_accounts = d->m_payees = d->m_tags = d->m_transactions = d->m_splits = d->m_securities = d->m_prices = d->m_currencies = d->m_schedules = d->m_reports = d->m_kvps = d->m_budgets = 0; d->m_hiIdInstitutions = d->m_hiIdPayees = d->m_hiIdTags = d->m_hiIdAccounts = d->m_hiIdTransactions = d->m_hiIdSchedules = d->m_hiIdSecurities = d->m_hiIdReports = d->m_hiIdBudgets = 0; d->m_onlineJobs = d->m_payeeIdentifier = 0; d->m_displayStatus = true; try { MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->writeInstitutions(); d->writePayees(); d->writeTags(); d->writeAccounts(); d->writeTransactions(); d->writeSchedules(); d->writeSecurities(); d->writePrices(); d->writeCurrencies(); d->writeReports(); d->writeBudgets(); d->writeOnlineJobs(); d->writeFileInfo(); // this seems to be nonsense, but it clears the dirty flag // as a side-effect. //m_storage->setLastModificationDate(m_storage->lastModificationDate()); // FIXME?? if (m_mode == 0) m_storage = NULL; // make sure the progress bar is not shown any longer d->signalProgress(-1, -1); d->m_displayStatus = false; + // this seems to be nonsense, but it clears the dirty flag + // as a side-effect. + d->m_storage->setLastModificationDate(d->m_storage->lastModificationDate()); return true; } catch (const QString &) { return false; } } QString MyMoneyStorageSql::lastError() const { Q_D(const MyMoneyStorageSql); return d->m_error; } // --------------- SQL Transaction (commit unit) handling ----------------------------------- void MyMoneyStorageSql::startCommitUnit(const QString& callingFunction) { Q_D(MyMoneyStorageSql); if (d->m_commitUnitStack.isEmpty()) { if (!transaction()) throw MYMONEYEXCEPTION(d->buildError(QSqlQuery(), callingFunction, "starting commit unit") + ' ' + callingFunction); } d->m_commitUnitStack.push(callingFunction); } bool MyMoneyStorageSql::endCommitUnit(const QString& callingFunction) { Q_D(MyMoneyStorageSql); // for now, we don't know if there were any changes made to the data so // we expect the data to have changed. This assumption causes some unnecessary // repaints of the UI here and there, but for now it's ok. If we can determine // that the commit() really changes the data, we can return that information // as value of this method. bool rc = true; if (d->m_commitUnitStack.isEmpty()) { throw MYMONEYEXCEPTION("Empty commit unit stack while trying to commit"); } if (callingFunction != d->m_commitUnitStack.top()) qDebug("%s", qPrintable(QString("%1 - %2 s/be %3").arg(Q_FUNC_INFO).arg(callingFunction).arg(d->m_commitUnitStack.top()))); d->m_commitUnitStack.pop(); if (d->m_commitUnitStack.isEmpty()) { //qDebug() << "Committing with " << QSqlQuery::refCount() << " queries"; if (!commit()) throw MYMONEYEXCEPTION(d->buildError(QSqlQuery(), callingFunction, "ending commit unit")); } return rc; } void MyMoneyStorageSql::cancelCommitUnit(const QString& callingFunction) { Q_D(MyMoneyStorageSql); if (d->m_commitUnitStack.isEmpty()) return; if (callingFunction != d->m_commitUnitStack.top()) qDebug("%s", qPrintable(QString("%1 - %2 s/be %3").arg(Q_FUNC_INFO).arg(callingFunction).arg(d->m_commitUnitStack.top()))); d->m_commitUnitStack.clear(); if (!rollback()) throw MYMONEYEXCEPTION(d->buildError(QSqlQuery(), callingFunction, "cancelling commit unit") + ' ' + callingFunction); } ///////////////////////////////////////////////////////////////////// void MyMoneyStorageSql::fillStorage() { Q_D(MyMoneyStorageSql); // if (!m_transactionListRead) // make sure we have loaded everything d->readTransactions(); // if (!m_payeeListRead) readPayees(); } //------------------------------ Write SQL routines ---------------------------------------- // **** Institutions **** void MyMoneyStorageSql::addInstitution(const MyMoneyInstitution& inst) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmInstitutions"].insertString()); QList iList; iList << inst; d->writeInstitutionList(iList , q); ++d->m_institutions; d->writeFileInfo(); } void MyMoneyStorageSql::modifyInstitution(const MyMoneyInstitution& inst) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmInstitutions"].updateString()); QVariantList kvpList; kvpList << inst.id(); d->deleteKeyValuePairs("OFXSETTINGS", kvpList); QList iList; iList << inst; d->writeInstitutionList(iList , q); d->writeFileInfo(); } void MyMoneyStorageSql::removeInstitution(const MyMoneyInstitution& inst) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << inst.id(); d->deleteKeyValuePairs("OFXSETTINGS", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmInstitutions"].deleteString()); q.bindValue(":id", inst.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Institution"))); // krazy:exclude=crashy --d->m_institutions; d->writeFileInfo(); } void MyMoneyStorageSql::addPayee(const MyMoneyPayee& payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayees"].insertString()); d->writePayee(payee, q); ++d->m_payees; QVariantList identIds; QList idents = payee.payeeIdentifiers(); // Store ids which have to be stored in the map table identIds.reserve(idents.count()); foreach (payeeIdentifier ident, idents) { try { // note: this changes ident addPayeeIdentifier(ident); identIds.append(ident.idString()); } catch (payeeIdentifier::empty&) { } } if (!identIds.isEmpty()) { // Create lists for batch processing QVariantList order; QVariantList payeeIdList; order.reserve(identIds.size()); payeeIdList.reserve(identIds.size()); for (int i = 0; i < identIds.size(); ++i) { order << i; payeeIdList << payee.id(); } q.prepare("INSERT INTO kmmPayeesPayeeIdentifier (payeeId, identifierId, userOrder) VALUES(?, ?, ?)"); q.bindValue(0, payeeIdList); q.bindValue(1, identIds); q.bindValue(2, order); if (!q.execBatch()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing payee's identifiers"))); // krazy:exclude=crashy } d->writeFileInfo(); } void MyMoneyStorageSql::modifyPayee(MyMoneyPayee payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayees"].updateString()); d->writePayee(payee, q); // Get a list of old identifiers first q.prepare("SELECT identifierId FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("modifying payee's identifiers (getting old values failed)"))); // krazy:exclude=crashy QStringList oldIdentIds; oldIdentIds.reserve(q.numRowsAffected()); while (q.next()) oldIdentIds << q.value(0).toString(); // Add new and modify old payeeIdentifiers foreach (payeeIdentifier ident, payee.payeeIdentifiers()) { if (ident.idString().isEmpty()) { payeeIdentifier oldIdent(ident); addPayeeIdentifier(ident); // addPayeeIdentifier could fail (throws an exception then) only remove old // identifier if new one is stored correctly payee.removePayeeIdentifier(oldIdent); payee.addPayeeIdentifier(ident); } else { modifyPayeeIdentifier(ident); payee.modifyPayeeIdentifier(ident); oldIdentIds.removeAll(ident.idString()); } } // Remove identifiers which are not used anymore foreach (QString idToRemove, oldIdentIds) { payeeIdentifier ident(fetchPayeeIdentifier(idToRemove)); removePayeeIdentifier(ident); } // Update relation table q.prepare("DELETE FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("modifying payee's identifiers (delete from mapping table)"))); // krazy:exclude=crashy // Get list again because modifiyPayeeIdentifier which is used above may change the id QList idents(payee.payeeIdentifiers()); QVariantList order; QVariantList payeeIdList; QVariantList identIdList; order.reserve(idents.size()); payeeIdList.reserve(idents.size()); identIdList.reserve(idents.size()); { QList::const_iterator end = idents.constEnd(); int i = 0; for (QList::const_iterator iter = idents.constBegin(); iter != end; ++iter, ++i) { order << i; payeeIdList << payee.id(); identIdList << iter->idString(); } } q.prepare("INSERT INTO kmmPayeesPayeeIdentifier (payeeId, userOrder, identifierId) VALUES(?, ?, ?)"); q.bindValue(0, payeeIdList); q.bindValue(1, order); q.bindValue(2, identIdList); if (!q.execBatch()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing payee's identifiers during modify"))); // krazy:exclude=crashy d->writeFileInfo(); } void MyMoneyStorageSql::modifyUserInfo(const MyMoneyPayee& payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayees"].updateString()); d->writePayee(payee, q, true); d->writeFileInfo(); } void MyMoneyStorageSql::removePayee(const MyMoneyPayee& payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); // Get identifiers first so we know which to delete q.prepare("SELECT identifierId FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("removing payee's identifiers (getting old values failed)"))); // krazy:exclude=crashy QStringList identIds; while (q.next()) identIds << q.value(0).toString(); QMap idents = fetchPayeeIdentifiers(identIds); foreach (payeeIdentifier ident, idents) { removePayeeIdentifier(ident); } // Delete entries from mapping table q.prepare("DELETE FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("removing payee's identifiers (delete from mapping table)"))); // krazy:exclude=crashy // Delete payee q.prepare(d->m_db.m_tables["kmmPayees"].deleteString()); q.bindValue(":id", payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Payee"))); // krazy:exclude=crashy --d->m_payees; d->writeFileInfo(); } // **** Tags **** void MyMoneyStorageSql::addTag(const MyMoneyTag& tag) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTags"].insertString()); d->writeTag(tag, q); ++d->m_tags; d->writeFileInfo(); } void MyMoneyStorageSql::modifyTag(const MyMoneyTag& tag) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTags"].updateString()); d->writeTag(tag, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeTag(const MyMoneyTag& tag) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTags"].deleteString()); q.bindValue(":id", tag.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Tag"))); // krazy:exclude=crashy --d->m_tags; d->writeFileInfo(); } // **** Accounts **** void MyMoneyStorageSql::addAccount(const MyMoneyAccount& acc) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmAccounts"].insertString()); QList aList; aList << acc; d->writeAccountList(aList, q); ++d->m_accounts; d->writeFileInfo(); } void MyMoneyStorageSql::modifyAccount(const MyMoneyAccount& acc) { QList aList; aList << acc; modifyAccountList(aList); } void MyMoneyStorageSql::modifyAccountList(const QList& acc) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmAccounts"].updateString()); QVariantList kvpList; foreach (const MyMoneyAccount& a, acc) { kvpList << a.id(); } d->deleteKeyValuePairs("ACCOUNT", kvpList); d->deleteKeyValuePairs("ONLINEBANKING", kvpList); d->writeAccountList(acc, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeAccount(const MyMoneyAccount& acc) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << acc.id(); d->deleteKeyValuePairs("ACCOUNT", kvpList); d->deleteKeyValuePairs("ONLINEBANKING", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmAccounts"].deleteString()); q.bindValue(":id", acc.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Account"))); // krazy:exclude=crashy --d->m_accounts; d->writeFileInfo(); } // **** Transactions and Splits **** void MyMoneyStorageSql::addTransaction(const MyMoneyTransaction& tx) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // add the transaction and splits QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTransactions"].insertString()); d->writeTransaction(tx.id(), tx, q, "N"); ++d->m_transactions; QList aList; // for each split account, update lastMod date, balance, txCount foreach (const MyMoneySplit& it_s, tx.splits()) { - MyMoneyAccount acc = d->m_storagePtr->account(it_s.accountId()); + MyMoneyAccount acc = d->m_storage->account(it_s.accountId()); ++d->m_transactionCountMap[acc.id()]; aList << acc; } modifyAccountList(aList); // in the fileinfo record, update lastMod, txCount, next TxId d->writeFileInfo(); } void MyMoneyStorageSql::modifyTransaction(const MyMoneyTransaction& tx) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // remove the splits of the old tx from the count table QSqlQuery q(*this); q.prepare("SELECT accountId FROM kmmSplits WHERE transactionId = :txId;"); q.bindValue(":txId", tx.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, "retrieving old splits")); while (q.next()) { QString id = q.value(0).toString(); --d->m_transactionCountMap[id]; } // add the transaction and splits q.prepare(d->m_db.m_tables["kmmTransactions"].updateString()); d->writeTransaction(tx.id(), tx, q, "N"); QList aList; // for each split account, update lastMod date, balance, txCount foreach (const MyMoneySplit& it_s, tx.splits()) { - MyMoneyAccount acc = d->m_storagePtr->account(it_s.accountId()); + MyMoneyAccount acc = d->m_storage->account(it_s.accountId()); ++d->m_transactionCountMap[acc.id()]; aList << acc; } modifyAccountList(aList); //writeSplits(tx.id(), "N", tx.splits()); // in the fileinfo record, update lastMod d->writeFileInfo(); } void MyMoneyStorageSql::removeTransaction(const MyMoneyTransaction& tx) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->deleteTransaction(tx.id()); --d->m_transactions; QList aList; // for each split account, update lastMod date, balance, txCount foreach (const MyMoneySplit& it_s, tx.splits()) { - MyMoneyAccount acc = d->m_storagePtr->account(it_s.accountId()); + MyMoneyAccount acc = d->m_storage->account(it_s.accountId()); --d->m_transactionCountMap[acc.id()]; aList << acc; } modifyAccountList(aList); // in the fileinfo record, update lastModDate, txCount d->writeFileInfo(); } // **** Schedules **** void MyMoneyStorageSql::addSchedule(const MyMoneySchedule& sched) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSchedules"].insertString()); d->writeSchedule(sched, q, true); ++d->m_schedules; d->writeFileInfo(); } void MyMoneyStorageSql::modifySchedule(const MyMoneySchedule& sched) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSchedules"].updateString()); d->writeSchedule(sched, q, false); d->writeFileInfo(); } void MyMoneyStorageSql::removeSchedule(const MyMoneySchedule& sched) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->deleteSchedule(sched.id()); --d->m_schedules; d->writeFileInfo(); } // **** Securities **** void MyMoneyStorageSql::addSecurity(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSecurities"].insertString()); d->writeSecurity(sec, q); ++d->m_securities; d->writeFileInfo(); } void MyMoneyStorageSql::modifySecurity(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << sec.id(); d->deleteKeyValuePairs("SECURITY", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSecurities"].updateString()); d->writeSecurity(sec, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeSecurity(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << sec.id(); d->deleteKeyValuePairs("SECURITY", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSecurities"].deleteString()); q.bindValue(":id", kvpList); if (!q.execBatch()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Security"))); --d->m_securities; d->writeFileInfo(); } // **** Prices **** void MyMoneyStorageSql::addPrice(const MyMoneyPrice& p) { Q_D(MyMoneyStorageSql); if (d->m_readingPrices) return; // the app always calls addPrice, whether or not there is already one there MyMoneyDbTransaction t(*this, Q_FUNC_INFO); bool newRecord = false; QSqlQuery q(*this); QString s = d->m_db.m_tables["kmmPrices"].selectAllString(false); s += " WHERE fromId = :fromId AND toId = :toId AND priceDate = :priceDate;"; q.prepare(s); q.bindValue(":fromId", p.from()); q.bindValue(":toId", p.to()); q.bindValue(":priceDate", p.date().toString(Qt::ISODate)); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("finding Price"))); // krazy:exclude=crashy if (q.next()) { q.prepare(d->m_db.m_tables["kmmPrices"].updateString()); } else { q.prepare(d->m_db.m_tables["kmmPrices"].insertString()); ++d->m_prices; newRecord = true; } q.bindValue(":fromId", p.from()); q.bindValue(":toId", p.to()); q.bindValue(":priceDate", p.date().toString(Qt::ISODate)); q.bindValue(":price", p.rate(QString()).toString()); - const MyMoneySecurity sec = d->m_storagePtr->security(p.to()); + const MyMoneySecurity sec = d->m_storage->security(p.to()); q.bindValue(":priceFormatted", p.rate(QString()).formatMoney("", sec.pricePrecision())); q.bindValue(":priceSource", p.source()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing Price"))); // krazy:exclude=crashy if (newRecord) d->writeFileInfo(); } void MyMoneyStorageSql::removePrice(const MyMoneyPrice& p) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPrices"].deleteString()); q.bindValue(":fromId", p.from()); q.bindValue(":toId", p.to()); q.bindValue(":priceDate", p.date().toString(Qt::ISODate)); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Price"))); // krazy:exclude=crashy --d->m_prices; d->writeFileInfo(); } // **** Currencies **** void MyMoneyStorageSql::addCurrency(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmCurrencies"].insertString()); d->writeCurrency(sec, q); ++d->m_currencies; d->writeFileInfo(); } void MyMoneyStorageSql::modifyCurrency(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmCurrencies"].updateString()); d->writeCurrency(sec, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeCurrency(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmCurrencies"].deleteString()); q.bindValue(":ISOcode", sec.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Currency"))); // krazy:exclude=crashy --d->m_currencies; d->writeFileInfo(); } void MyMoneyStorageSql::addReport(const MyMoneyReport& rep) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmReportConfig"].insertString()); d->writeReport(rep, q); ++d->m_reports; d->writeFileInfo(); } void MyMoneyStorageSql::modifyReport(const MyMoneyReport& rep) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmReportConfig"].updateString()); d->writeReport(rep, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeReport(const MyMoneyReport& rep) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare("DELETE FROM kmmReportConfig WHERE id = :id"); q.bindValue(":id", rep.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Report"))); // krazy:exclude=crashy --d->m_reports; d->writeFileInfo(); } void MyMoneyStorageSql::addBudget(const MyMoneyBudget& bud) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmBudgetConfig"].insertString()); d->writeBudget(bud, q); ++d->m_budgets; d->writeFileInfo(); } void MyMoneyStorageSql::modifyBudget(const MyMoneyBudget& bud) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmBudgetConfig"].updateString()); d->writeBudget(bud, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeBudget(const MyMoneyBudget& bud) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmBudgetConfig"].deleteString()); q.bindValue(":id", bud.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Budget"))); // krazy:exclude=crashy --d->m_budgets; d->writeFileInfo(); } void MyMoneyStorageSql::addOnlineJob(const onlineJob& job) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare("INSERT INTO kmmOnlineJobs (id, type, jobSend, bankAnswerDate, state, locked) VALUES(:id, :type, :jobSend, :bankAnswerDate, :state, :locked);"); d->writeOnlineJob(job, q); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing onlineJob"))); // krazy:exclude=crashy ++d->m_onlineJobs; try { // Save online task d->insertStorableObject(*job.constTask(), job.id()); } catch (onlineJob::emptyTask&) { } } void MyMoneyStorageSql::modifyOnlineJob(const onlineJob& job) { Q_D(MyMoneyStorageSql); Q_ASSERT(!job.id().isEmpty()); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery query(*this); query.prepare(QLatin1String( "UPDATE kmmOnlineJobs SET " " type = :type, " " jobSend = :jobSend, " " bankAnswerDate = :bankAnswerDate, " " state = :state, " " locked = :locked " " WHERE id = :id" )); d->writeOnlineJob(job, query); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("writing onlineJob"))); // krazy:exclude=crashy try { // Modify online task d->updateStorableObject(*job.constTask(), job.id()); } catch (onlineJob::emptyTask&) { // If there is no task attached this is fine as well } } void MyMoneyStorageSql::removeOnlineJob(const onlineJob& job) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // Remove onlineTask first, because it could have a contraint // which could block the removal of the onlineJob try { // Remove task d->deleteStorableObject(*job.constTask(), job.id()); } catch (onlineJob::emptyTask&) { } QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmOnlineJobs"].deleteString()); q.bindValue(":id", job.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting onlineJob"))); // krazy:exclude=crashy --d->m_onlineJobs; } void MyMoneyStorageSql::addPayeeIdentifier(payeeIdentifier& ident) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); ident = payeeIdentifier(incrementPayeeIdentfierId(), ident); QSqlQuery q(*this); q.prepare("INSERT INTO kmmPayeeIdentifier (id, type) VALUES(:id, :type)"); d->writePayeeIdentifier(ident, q); ++d->m_payeeIdentifier; try { d->insertStorableObject(*ident.data(), ident.idString()); } catch (payeeIdentifier::empty&) { } } void MyMoneyStorageSql::modifyPayeeIdentifier(const payeeIdentifier& ident) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare("SELECT type FROM kmmPayeeIdentifier WHERE id = ?"); q.bindValue(0, ident.idString()); if (!q.exec() || !q.next()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("modifying payeeIdentifier"))); // krazy:exclude=crashy bool typeChanged = (q.value(0).toString() != ident.iid()); if (typeChanged) { // Delete old identifier if type changed const payeeIdentifier oldIdent(fetchPayeeIdentifier(ident.idString())); try { d->deleteStorableObject(*oldIdent.data(), ident.idString()); } catch (payeeIdentifier::empty&) { // Note: this should not happen because the ui does not offer a way to change // the type of an payeeIdentifier if it was not correctly loaded. throw MYMONEYEXCEPTION(QLatin1String("Could not modify payeeIdentifier '") + ident.idString() + QLatin1String("' because type changed and could not remove identifier of old type. Maybe a plugin is missing?") ); // krazy:exclude=crashy } } q.prepare("UPDATE kmmPayeeIdentifier SET type = :type WHERE id = :id"); d->writePayeeIdentifier(ident, q); try { if (typeChanged) d->insertStorableObject(*ident.data(), ident.idString()); else d->updateStorableObject(*ident.data(), ident.idString()); } catch (payeeIdentifier::empty&) { } } void MyMoneyStorageSql::removePayeeIdentifier(const payeeIdentifier& ident) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // Remove first, the table could have a contraint which prevents removal // of row in kmmPayeeIdentifier try { d->deleteStorableObject(*ident.data(), ident.idString()); } catch (payeeIdentifier::empty&) { } QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayeeIdentifier"].deleteString()); q.bindValue(":id", ident.idString()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting payeeIdentifier"))); // krazy:exclude=crashy --d->m_payeeIdentifier; } // **** Key/value pairs **** //******************************** read SQL routines ************************************** /*void MyMoneyStorageSql::setVersion (const QString& version) { m_dbVersion = version.section('.', 0, 0).toUInt(); m_minorVersion = version.section('.', 1, 1).toUInt(); // Okay, I made a cockup by forgetting to include a fixversion in the database // design, so we'll use the minor version as fix level (similar to VERSION // and FIXVERSION in XML file format). A second mistake was setting minor version to 1 // in the first place, so we need to subtract one on reading and add one on writing (sigh)!! m_storage->setFileFixVersion( m_minorVersion - 1); }*/ QMap MyMoneyStorageSql::fetchInstitutions(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int institutionsNb = (idList.isEmpty() ? d->m_institutions : idList.size()); d->signalProgress(0, institutionsNb, QObject::tr("Loading institutions...")); int progress = 0; QMap iList; ulong lastId = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmInstitutions"]; QSqlQuery sq(*const_cast (this)); sq.prepare("SELECT id from kmmAccounts where institutionId = :id"); QSqlQuery query(*const_cast (this)); QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE"; for (int i = 0; i < idList.count(); ++i) queryString += QString(" id = :id%1 OR").arg(i); queryString = queryString.left(queryString.length() - 2); } if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Institution"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int managerCol = t.fieldNumber("manager"); int routingCodeCol = t.fieldNumber("routingCode"); int addressStreetCol = t.fieldNumber("addressStreet"); int addressCityCol = t.fieldNumber("addressCity"); int addressZipcodeCol = t.fieldNumber("addressZipcode"); int telephoneCol = t.fieldNumber("telephone"); while (query.next()) { MyMoneyInstitution inst; QString iid = GETSTRING(idCol); inst.setName(GETSTRING(nameCol)); inst.setManager(GETSTRING(managerCol)); inst.setSortcode(GETSTRING(routingCodeCol)); inst.setStreet(GETSTRING(addressStreetCol)); inst.setCity(GETSTRING(addressCityCol)); inst.setPostcode(GETSTRING(addressZipcodeCol)); inst.setTelephone(GETSTRING(telephoneCol)); // get list of subaccounts sq.bindValue(":id", iid); if (!sq.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Institution AccountList"))); // krazy:exclude=crashy QStringList aList; while (sq.next()) aList.append(sq.value(0).toString()); foreach (const QString& it, aList) inst.addAccountId(it); iList[iid] = MyMoneyInstitution(iid, inst); ulong id = MyMoneyUtils::extractId(iid); if (id > lastId) lastId = id; d->signalProgress(++progress, 0); } return iList; } QMap MyMoneyStorageSql::fetchInstitutions() const { return fetchInstitutions(QStringList(), false); } void MyMoneyStorageSql::readPayees(const QString& id) { QList list; list.append(id); readPayees(list); } void MyMoneyStorageSql::readPayees(const QList& pid) { Q_D(MyMoneyStorageSql); try { d->m_storage->loadPayees(fetchPayees(pid)); - d->m_storage->loadPayeeId(getNextPayeeId()); } catch (const MyMoneyException &) { } // if (pid.isEmpty()) m_payeeListRead = true; } void MyMoneyStorageSql::readPayees() { readPayees(QList()); } QMap MyMoneyStorageSql::fetchPayees(const QStringList& idList, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) { int payeesNb = (idList.isEmpty() ? d->m_payees : idList.size()); d->signalProgress(0, payeesNb, QObject::tr("Loading payees...")); } int progress = 0; QMap pList; QSqlQuery query(*const_cast (this)); QString queryString = QLatin1String("SELECT kmmPayees.id AS id, kmmPayees.name AS name, kmmPayees.reference AS reference, " " kmmPayees.email AS email, kmmPayees.addressStreet AS addressStreet, kmmPayees.addressCity AS addressCity, kmmPayees.addressZipcode AS addressZipcode, " " kmmPayees.addressState AS addressState, kmmPayees.telephone AS telephone, kmmPayees.notes AS notes, " " kmmPayees.defaultAccountId AS defaultAccountId, kmmPayees.matchData AS matchData, kmmPayees.matchIgnoreCase AS matchIgnoreCase, " " kmmPayees.matchKeys AS matchKeys, " " kmmPayeesPayeeIdentifier.identifierId AS identId " " FROM ( SELECT * FROM kmmPayees "); if (!idList.isEmpty()) { // Create WHERE clause if needed queryString += QLatin1String(" WHERE id IN ("); queryString += QString("?, ").repeated(idList.length()); queryString.chop(2); // remove ", " from end queryString += QLatin1Char(')'); } queryString += QLatin1String( " ) kmmPayees " " LEFT OUTER JOIN kmmPayeesPayeeIdentifier ON kmmPayees.Id = kmmPayeesPayeeIdentifier.payeeId " // The order is used below " ORDER BY kmmPayees.id, kmmPayeesPayeeIdentifier.userOrder;"); query.prepare(queryString); if (!idList.isEmpty()) { // Bind values QStringList::const_iterator end = idList.constEnd(); for (QStringList::const_iterator iter = idList.constBegin(); iter != end; ++iter) { query.addBindValue(*iter); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Payee"))); // krazy:exclude=crashy const QSqlRecord record = query.record(); const int idCol = record.indexOf("id"); const int nameCol = record.indexOf("name"); const int referenceCol = record.indexOf("reference"); const int emailCol = record.indexOf("email"); const int addressStreetCol = record.indexOf("addressStreet"); const int addressCityCol = record.indexOf("addressCity"); const int addressZipcodeCol = record.indexOf("addressZipcode"); const int addressStateCol = record.indexOf("addressState"); const int telephoneCol = record.indexOf("telephone"); const int notesCol = record.indexOf("notes"); const int defaultAccountIdCol = record.indexOf("defaultAccountId"); const int matchDataCol = record.indexOf("matchData"); const int matchIgnoreCaseCol = record.indexOf("matchIgnoreCase"); const int matchKeysCol = record.indexOf("matchKeys"); const int identIdCol = record.indexOf("identId"); query.next(); while (query.isValid()) { QString pid; QString boolChar; MyMoneyPayee payee; uint type; bool ignoreCase; QString matchKeys; pid = GETSTRING(idCol); payee.setName(GETSTRING(nameCol)); payee.setReference(GETSTRING(referenceCol)); payee.setEmail(GETSTRING(emailCol)); payee.setAddress(GETSTRING(addressStreetCol)); payee.setCity(GETSTRING(addressCityCol)); payee.setPostcode(GETSTRING(addressZipcodeCol)); payee.setState(GETSTRING(addressStateCol)); payee.setTelephone(GETSTRING(telephoneCol)); payee.setNotes(GETSTRING(notesCol)); payee.setDefaultAccountId(GETSTRING(defaultAccountIdCol)); type = GETINT(matchDataCol); ignoreCase = (GETSTRING(matchIgnoreCaseCol) == "Y"); matchKeys = GETSTRING(matchKeysCol); payee.setMatchData(static_cast(type), ignoreCase, matchKeys); // Get payeeIdentifier ids QStringList identifierIds; do { identifierIds.append(GETSTRING(identIdCol)); } while (query.next() && GETSTRING(idCol) == pid); // as long as the payeeId is unchanged // Fetch and save payeeIdentifier if (!identifierIds.isEmpty()) { QList< ::payeeIdentifier > identifier = fetchPayeeIdentifiers(identifierIds).values(); payee.resetPayeeIdentifiers(identifier); } if (pid == "USER") d->m_storage->setUser(payee); else pList[pid] = MyMoneyPayee(pid, payee); if (d->m_displayStatus) d->signalProgress(++progress, 0); } return pList; } QMap MyMoneyStorageSql::fetchPayees() const { return fetchPayees(QStringList(), false); } void MyMoneyStorageSql::readTags(const QString& id) { QList list; list.append(id); readTags(list); } void MyMoneyStorageSql::readTags(const QList& pid) { Q_D(MyMoneyStorageSql); try { d->m_storage->loadTags(fetchTags(pid)); d->readFileInfo(); - d->m_storage->loadTagId(d->m_hiIdTags); } catch (const MyMoneyException &) { } // if (pid.isEmpty()) m_tagListRead = true; } void MyMoneyStorageSql::readTags() { readTags(QList()); } QMap MyMoneyStorageSql::fetchOnlineJobs(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); Q_UNUSED(forUpdate); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) d->signalProgress(0, idList.isEmpty() ? d->m_onlineJobs : idList.size(), QObject::tr("Loading online banking data...")); // Create query QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare("SELECT id, type, jobSend, bankAnswerDate, state, locked FROM kmmOnlineJobs;"); } else { QString queryIdSet = QString("?, ").repeated(idList.length()); queryIdSet.chop(2); query.prepare(QLatin1String("SELECT id, type, jobSend, bankAnswerDate, state, locked FROM kmmOnlineJobs WHERE id IN (") + queryIdSet + QLatin1String(");")); QStringList::const_iterator end = idList.constEnd(); for (QStringList::const_iterator iter = idList.constBegin(); iter != end; ++iter) { query.addBindValue(*iter); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading onlineJobs"))); // krazy:exclude=crashy // Create onlineJobs int progress = 0; QMap jobList; while (query.next()) { const QString& id = query.value(0).toString(); onlineTask *const task = onlineJobAdministration::instance()->createOnlineTaskFromSqlDatabase(query.value(1).toString(), id, *this); onlineJob job = onlineJob(task, id); job.setJobSend(query.value(2).toDateTime()); onlineJob::sendingState state; const QString stateString = query.value(4).toString(); if (stateString == "acceptedByBank") state = onlineJob::acceptedByBank; else if (stateString == "rejectedByBank") state = onlineJob::rejectedByBank; else if (stateString == "abortedByUser") state = onlineJob::abortedByUser; else if (stateString == "sendingError") state = onlineJob::sendingError; else // includes: stateString == "noBankAnswer" state = onlineJob::noBankAnswer; job.setBankAnswer(state, query.value(4).toDateTime()); job.setLock(query.value(5).toString() == QLatin1String("Y") ? true : false); jobList.insert(job.id(), job); if (d->m_displayStatus) d->signalProgress(++progress, 0); } return jobList; } QMap MyMoneyStorageSql::fetchOnlineJobs() const { return fetchOnlineJobs(QStringList(), false); } payeeIdentifier MyMoneyStorageSql::fetchPayeeIdentifier(const QString& id) const { QMap list = fetchPayeeIdentifiers(QStringList(id)); QMap::const_iterator iter = list.constFind(id); if (iter == list.constEnd()) throw MYMONEYEXCEPTION(QLatin1String("payeeIdentifier with id '") + id + QLatin1String("' not found.")); // krazy:exclude=crashy return *iter; } QMap< QString, payeeIdentifier > MyMoneyStorageSql::fetchPayeeIdentifiers(const QStringList& idList) const { Q_D(const MyMoneyStorageSql); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); // Create query QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare("SELECT id, type FROM kmmPayeeIdentifier;"); } else { QString queryIdSet = QString("?, ").repeated(idList.length()); queryIdSet.chop(2); // remove ", " from end query.prepare(QLatin1String("SELECT id, type FROM kmmPayeeIdentifier WHERE id IN (") + queryIdSet + QLatin1String(");")); QStringList::const_iterator end = idList.constEnd(); for (QStringList::const_iterator iter = idList.constBegin(); iter != end; ++iter) { query.addBindValue(*iter); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading payee identifiers"))); // krazy:exclude=crashy QMap identList; while (query.next()) { const QString id = query.value(0).toString(); identList.insert(id, payeeIdentifierLoader::instance()->createPayeeIdentifierFromSqlDatabase(*this, query.value(1).toString(), id)); } return identList; } QMap< QString, payeeIdentifier > MyMoneyStorageSql::fetchPayeeIdentifiers() const { return fetchPayeeIdentifiers(QStringList()); } QMap MyMoneyStorageSql::fetchTags(const QStringList& idList, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) { int tagsNb = (idList.isEmpty() ? d->m_tags : idList.size()); d->signalProgress(0, tagsNb, QObject::tr("Loading tags...")); } else { // if (m_tagListRead) return; } int progress = 0; QMap taList; //ulong lastId; const MyMoneyDbTable& t = d->m_db.m_tables["kmmTags"]; QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare(t.selectAllString()); } else { QString whereClause = " where ("; QString itemConnector = ""; foreach (const QString& it, idList) { whereClause.append(QString("%1id = '%2'").arg(itemConnector).arg(it)); itemConnector = " or "; } whereClause += ')'; query.prepare(t.selectAllString(false) + whereClause); } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Tag"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int notesCol = t.fieldNumber("notes"); int tagColorCol = t.fieldNumber("tagColor"); int closedCol = t.fieldNumber("closed"); while (query.next()) { QString pid; QString boolChar; MyMoneyTag tag; pid = GETSTRING(idCol); tag.setName(GETSTRING(nameCol)); tag.setNotes(GETSTRING(notesCol)); tag.setClosed((GETSTRING(closedCol) == "Y")); tag.setTagColor(QColor(GETSTRING(tagColorCol))); taList[pid] = MyMoneyTag(pid, tag); if (d->m_displayStatus) d->signalProgress(++progress, 0); } return taList; } QMap MyMoneyStorageSql::fetchTags() const { return fetchTags(QStringList(), false); } QMap MyMoneyStorageSql::fetchAccounts(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int accountsNb = (idList.isEmpty() ? d->m_accounts : idList.size()); d->signalProgress(0, accountsNb, QObject::tr("Loading accounts...")); int progress = 0; QMap accList; QStringList kvpAccountList(idList); const MyMoneyDbTable& t = d->m_db.m_tables["kmmAccounts"]; QSqlQuery query(*const_cast (this)); QSqlQuery sq(*const_cast (this)); QString childQueryString = "SELECT id, parentId FROM kmmAccounts WHERE "; QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE id IN ("; childQueryString += " parentId IN ("; QString inString; for (int i = 0; i < idList.count(); ++i) { inString += QString(":id%1, ").arg(i); } inString = inString.left(inString.length() - 2) + ')'; queryString += inString; childQueryString += inString; } else { childQueryString += " NOT parentId IS NULL"; } queryString += " ORDER BY id"; childQueryString += " ORDER BY parentid, id"; if (forUpdate) { queryString += d->m_driver->forUpdateString(); childQueryString += d->m_driver->forUpdateString(); } query.prepare(queryString); sq.prepare(childQueryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); sq.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Account"))); // krazy:exclude=crashy if (!sq.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading subAccountList"))); // krazy:exclude=crashy // Reserve enough space for all values. Approximate it with the size of the // idList in case the db doesn't support reporting the size of the // resultset to the caller. //FIXME: this is for if/when there is a QHash conversion //accList.reserve(q.size() > 0 ? q.size() : idList.size()); static const int idCol = t.fieldNumber("id"); static const int institutionIdCol = t.fieldNumber("institutionId"); static const int parentIdCol = t.fieldNumber("parentId"); static const int lastReconciledCol = t.fieldNumber("lastReconciled"); static const int lastModifiedCol = t.fieldNumber("lastModified"); static const int openingDateCol = t.fieldNumber("openingDate"); static const int accountNumberCol = t.fieldNumber("accountNumber"); static const int accountTypeCol = t.fieldNumber("accountType"); static const int accountNameCol = t.fieldNumber("accountName"); static const int descriptionCol = t.fieldNumber("description"); static const int currencyIdCol = t.fieldNumber("currencyId"); static const int balanceCol = t.fieldNumber("balance"); static const int transactionCountCol = t.fieldNumber("transactionCount"); while (query.next()) { QString aid; QString balance; MyMoneyAccount acc; aid = GETSTRING(idCol); acc.setInstitutionId(GETSTRING(institutionIdCol)); acc.setParentAccountId(GETSTRING(parentIdCol)); acc.setLastReconciliationDate(GETDATE_D(lastReconciledCol)); acc.setLastModified(GETDATE_D(lastModifiedCol)); acc.setOpeningDate(GETDATE_D(openingDateCol)); acc.setNumber(GETSTRING(accountNumberCol)); acc.setAccountType(static_cast(GETINT(accountTypeCol))); acc.setName(GETSTRING(accountNameCol)); acc.setDescription(GETSTRING(descriptionCol)); acc.setCurrencyId(GETSTRING(currencyIdCol)); acc.setBalance(MyMoneyMoney(GETSTRING(balanceCol))); const_cast (this)->d_func()->m_transactionCountMap[aid] = (ulong) GETULL(transactionCountCol); // Process any key value pair if (idList.empty()) kvpAccountList.append(aid); accList.insert(aid, MyMoneyAccount(aid, acc)); if (acc.value("PreferredAccount") == "Yes") { const_cast (this)->d_func()->m_preferred.addAccount(aid); } d->signalProgress(++progress, 0); } QMap::Iterator it_acc; QMap::Iterator accListEnd = accList.end(); while (sq.next()) { it_acc = accList.find(sq.value(1).toString()); if (it_acc != accListEnd && it_acc.value().id() == sq.value(1).toString()) { while (sq.isValid() && it_acc != accListEnd && it_acc.value().id() == sq.value(1).toString()) { it_acc.value().addAccountId(sq.value(0).toString()); sq.next(); } sq.previous(); } } //TODO: There should be a better way than this. What's below is O(n log n) or more, // where it may be able to be done in O(n), if things are just right. // The operator[] call in the loop is the most expensive call in this function, according // to several profile runs. QHash kvpResult = d->readKeyValuePairs("ACCOUNT", kvpAccountList); QHash ::const_iterator kvp_end = kvpResult.constEnd(); for (QHash ::const_iterator it_kvp = kvpResult.constBegin(); it_kvp != kvp_end; ++it_kvp) { accList[it_kvp.key()].setPairs(it_kvp.value().pairs()); } kvpResult = d->readKeyValuePairs("ONLINEBANKING", kvpAccountList); kvp_end = kvpResult.constEnd(); for (QHash ::const_iterator it_kvp = kvpResult.constBegin(); it_kvp != kvp_end; ++it_kvp) { accList[it_kvp.key()].setOnlineBankingSettings(it_kvp.value()); } return accList; } QMap MyMoneyStorageSql::fetchAccounts() const { return fetchAccounts(QStringList(), false); } QMap MyMoneyStorageSql::fetchBalance(const QStringList& idList, const QDate& date) const { Q_D(const MyMoneyStorageSql); QMap returnValue; QSqlQuery query(*const_cast (this)); QString queryString = "SELECT action, shares, accountId, postDate " "FROM kmmSplits WHERE txType = 'N'"; if (idList.count() > 0) { queryString += "AND accountId in ("; for (int i = 0; i < idList.count(); ++i) { queryString += QString(":id%1, ").arg(i); } queryString = queryString.left(queryString.length() - 2) + ')'; } // SQLite stores dates as YYYY-MM-DDTHH:mm:ss with 0s for the time part. This makes // the <= operator misbehave when the date matches. To avoid this, add a day to the // requested date and use the < operator. if (date.isValid() && !date.isNull()) queryString += QString(" AND postDate < '%1'").arg(date.addDays(1).toString(Qt::ISODate)); queryString += " ORDER BY accountId, postDate;"; //DBG(queryString); query.prepare(queryString); int i = 0; foreach (const QString& bindVal, idList) { query.bindValue(QString(":id%1").arg(i), bindVal); ++i; } if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("fetching balance"))); QString id; QString oldId; MyMoneyMoney temp; while (query.next()) { id = query.value(2).toString(); // If the old ID does not match the new ID, then the account being summed has changed. // Write the balance into the returnValue map and update the oldId to the current one. if (id != oldId) { if (!oldId.isEmpty()) { returnValue.insert(oldId, temp); temp = 0; } oldId = id; } if (MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares) == query.value(0).toString()) temp *= MyMoneyMoney(query.value(1).toString()); else temp += MyMoneyMoney(query.value(1).toString()); } // Do not forget the last id in the list. returnValue.insert(id, temp); // Return the map. return returnValue; } void MyMoneyStorageSql::readTransactions(const MyMoneyTransactionFilter& filter) { Q_D(MyMoneyStorageSql); try { d->m_storage->loadTransactions(fetchTransactions(filter)); - d->m_storage->loadTransactionId(getNextTransactionId()); } catch (const MyMoneyException &) { throw; } } QMap MyMoneyStorageSql::fetchTransactions(const QString& tidList, const QString& dateClause, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); // if (m_transactionListRead) return; // all list already in memory if (d->m_displayStatus) { int transactionsNb = (tidList.isEmpty() ? d->m_transactions : tidList.size()); d->signalProgress(0, transactionsNb, QObject::tr("Loading transactions...")); } int progress = 0; // m_payeeList.clear(); QString whereClause = " WHERE txType = 'N' "; if (! tidList.isEmpty()) { whereClause += " AND id IN " + tidList; } if (!dateClause.isEmpty()) whereClause += " and " + dateClause; const MyMoneyDbTable& t = d->m_db.m_tables["kmmTransactions"]; QSqlQuery query(*const_cast (this)); query.prepare(t.selectAllString(false) + whereClause + " ORDER BY id;"); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Transaction"))); // krazy:exclude=crashy const MyMoneyDbTable& ts = d->m_db.m_tables["kmmSplits"]; whereClause = " WHERE txType = 'N' "; if (! tidList.isEmpty()) { whereClause += " AND transactionId IN " + tidList; } if (!dateClause.isEmpty()) whereClause += " and " + dateClause; QSqlQuery qs(*const_cast (this)); QString splitQuery = ts.selectAllString(false) + whereClause + " ORDER BY transactionId, splitId;"; qs.prepare(splitQuery); if (!qs.exec()) throw MYMONEYEXCEPTION(d->buildError(qs, Q_FUNC_INFO, "reading Splits")); // krazy:exclude=crashy QString splitTxId = "ZZZ"; MyMoneySplit s; if (qs.next()) { splitTxId = qs.value(0).toString(); d->readSplit(s, qs); } else { splitTxId = "ZZZ"; } QMap txMap; QStringList txList; int idCol = t.fieldNumber("id"); int postDateCol = t.fieldNumber("postDate"); int memoCol = t.fieldNumber("memo"); int entryDateCol = t.fieldNumber("entryDate"); int currencyIdCol = t.fieldNumber("currencyId"); int bankIdCol = t.fieldNumber("bankId"); while (query.next()) { MyMoneyTransaction tx; QString txId = GETSTRING(idCol); tx.setPostDate(GETDATE_D(postDateCol)); tx.setMemo(GETSTRING(memoCol)); tx.setEntryDate(GETDATE_D(entryDateCol)); tx.setCommodity(GETSTRING(currencyIdCol)); tx.setBankID(GETSTRING(bankIdCol)); // skip all splits while the transaction id of the split is less than // the transaction id of the current transaction. Don't forget to check // for the ZZZ flag for the end of the list. while (txId < splitTxId && splitTxId != "ZZZ") { if (qs.next()) { splitTxId = qs.value(0).toString(); d->readSplit(s, qs); } else { splitTxId = "ZZZ"; } } // while the split transaction id matches the current transaction id, // add the split to the current transaction. Set the ZZZ flag if // all splits for this transaction have been read. while (txId == splitTxId) { tx.addSplit(s); if (qs.next()) { splitTxId = qs.value(0).toString(); d->readSplit(s, qs); } else { splitTxId = "ZZZ"; } } // Process any key value pair if (! txId.isEmpty()) { txList.append(txId); tx = MyMoneyTransaction(txId, tx); txMap.insert(tx.uniqueSortKey(), tx); } } // get the kvps QHash kvpMap = d->readKeyValuePairs("TRANSACTION", txList); QMap::Iterator txMapEnd = txMap.end(); for (QMap::Iterator i = txMap.begin(); i != txMapEnd; ++i) { i.value().setPairs(kvpMap[i.value().id()].pairs()); if (d->m_displayStatus) d->signalProgress(++progress, 0); } if ((tidList.isEmpty()) && (dateClause.isEmpty())) { //qDebug("setting full list read"); } return txMap; } QMap MyMoneyStorageSql::fetchTransactions(const QString& tidList) const { return fetchTransactions(tidList, QString(), false); } QMap MyMoneyStorageSql::fetchTransactions() const { return fetchTransactions(QString(), QString(), false); } QMap MyMoneyStorageSql::fetchTransactions(const MyMoneyTransactionFilter& filter) const { Q_D(const MyMoneyStorageSql); // analyze the filter // if (m_transactionListRead) return; // all list already in memory // if the filter is restricted to certain accounts/categories // check if we already have them all in memory QStringList accounts; QString inQuery; filter.accounts(accounts); filter.categories(accounts); // QStringList::iterator it; // bool allAccountsLoaded = true; // for (it = accounts.begin(); it != accounts.end(); ++it) { // if (m_accountsLoaded.find(*it) == m_accountsLoaded.end()) { // allAccountsLoaded = false; // break; // } // } // if (allAccountsLoaded) return; /* Some filter combinations do not lend themselves to implementation * in SQL, or are likely to require such extensive reading of the database * as to make it easier to just read everything into memory. */ bool canImplementFilter = true; MyMoneyMoney m1, m2; if (filter.amountFilter(m1, m2)) { d->alert("Amount Filter Set"); canImplementFilter = false; } QString n1, n2; if (filter.numberFilter(n1, n2)) { d->alert("Number filter set"); canImplementFilter = false; } int t1; if (filter.firstType(t1)) { d->alert("Type filter set"); canImplementFilter = false; } // int s1; // if (filter.firstState(s1)) { // alert("State filter set"); // canImplementFilter = false; // } QRegExp t2; if (filter.textFilter(t2)) { d->alert("text filter set"); canImplementFilter = false; } MyMoneyTransactionFilter::FilterSet s = filter.filterSet(); if (s.singleFilter.validityFilter) { d->alert("Validity filter set"); canImplementFilter = false; } if (!canImplementFilter) { QMap transactionList = fetchTransactions(); QMap::ConstIterator it_t; std::remove_if(transactionList.begin(), transactionList.end(), FilterFail(filter)); return transactionList; } bool splitFilterActive = false; // the split filter is active if we are selecting on fields in the split table // get start and end dates QDate start = filter.fromDate(); QDate end = filter.toDate(); // not entirely sure if the following is correct, but at best, saves a lot of reads, at worst // it only causes us to read a few more transactions that strictly necessary (I think...) if (start == d->m_startDate) start = QDate(); bool txFilterActive = ((start != QDate()) || (end != QDate())); // and this for fields in the transaction table QString whereClause = ""; QString subClauseconnector = " where txType = 'N' and "; // payees QStringList payees; if (filter.payees(payees)) { QString itemConnector = "payeeId in ("; QString payeesClause = ""; foreach (const QString& it, payees) { payeesClause.append(QString("%1'%2'") .arg(itemConnector).arg(it)); itemConnector = ", "; } if (!payeesClause.isEmpty()) { whereClause += subClauseconnector + payeesClause + ')'; subClauseconnector = " and "; } splitFilterActive = true; } //tags QStringList tags; if (filter.tags(tags)) { QString itemConnector = "splitId in ( SELECT splitId from kmmTagSplits where kmmTagSplits.transactionId = kmmSplits.transactionId and tagId in ("; QString tagsClause = ""; foreach (const QString& it, tags) { tagsClause.append(QString("%1'%2'") .arg(itemConnector).arg(it)); itemConnector = ", "; } if (!tagsClause.isEmpty()) { whereClause += subClauseconnector + tagsClause + ')'; subClauseconnector = " and "; } splitFilterActive = true; } // accounts and categories if (!accounts.isEmpty()) { splitFilterActive = true; QString itemConnector = "accountId in ("; QString accountsClause = ""; foreach (const QString& it, accounts) { accountsClause.append(QString("%1 '%2'") .arg(itemConnector).arg(it)); itemConnector = ", "; } if (!accountsClause.isEmpty()) { whereClause += subClauseconnector + accountsClause + ')'; subClauseconnector = " and ("; } } // split states QList splitStates; if (filter.states(splitStates)) { splitFilterActive = true; QString itemConnector = " reconcileFlag IN ("; QString statesClause = ""; foreach (int it, splitStates) { statesClause.append(QString(" %1 '%2'").arg(itemConnector) .arg(d->splitState(TransactionFilter::State(it)))); itemConnector = ','; } if (!statesClause.isEmpty()) { whereClause += subClauseconnector + statesClause + ')'; subClauseconnector = " and ("; } } // I've given up trying to work out the logic. we keep getting the wrong number of close brackets int obc = whereClause.count('('); int cbc = whereClause.count(')'); if (cbc > obc) { qDebug() << "invalid where clause " << whereClause; qFatal("aborting"); } while (cbc < obc) { whereClause.append(')'); cbc++; } // if the split filter is active, but the where clause and the date filter is empty // it means we already have all the transactions for the specified filter // in memory, so just exit if ((splitFilterActive) && (whereClause.isEmpty()) && (!txFilterActive)) { qDebug("all transactions already in storage"); return fetchTransactions(); } // if we have neither a split filter, nor a tx (date) filter // it's effectively a read all if ((!splitFilterActive) && (!txFilterActive)) { //qDebug("reading all transactions"); return fetchTransactions(); } // build a date clause for the transaction table QString dateClause; QString connector = ""; if (end != QDate()) { dateClause = QString("(postDate < '%1')").arg(end.addDays(1).toString(Qt::ISODate)); connector = " and "; } if (start != QDate()) { dateClause += QString("%1 (postDate >= '%2')").arg(connector).arg(start.toString(Qt::ISODate)); } // now get a list of transaction ids // if we have only a date filter, we need to build the list from the tx table // otherwise we need to build from the split table if (splitFilterActive) { inQuery = QString("(select distinct transactionId from kmmSplits %1)").arg(whereClause); } else { inQuery = QString("(select distinct id from kmmTransactions where %1)").arg(dateClause); txFilterActive = false; // kill off the date filter now } return fetchTransactions(inQuery, dateClause); //FIXME: if we have an accounts-only filter, recalc balances on loaded accounts } ulong MyMoneyStorageSql::transactionCount(const QString& aid) const { Q_D(const MyMoneyStorageSql); if (aid.isEmpty()) return d->m_transactions; else return d->m_transactionCountMap[aid]; } QHash MyMoneyStorageSql::transactionCountMap() const { Q_D(const MyMoneyStorageSql); return d->m_transactionCountMap; } bool MyMoneyStorageSql::isReferencedByTransaction(const QString& id) const { Q_D(const MyMoneyStorageSql); //FIXME-ALEX should I add sub query for kmmTagSplits here? QSqlQuery q(*const_cast (this)); q.prepare("SELECT COUNT(*) FROM kmmTransactions " "INNER JOIN kmmSplits ON kmmTransactions.id = kmmSplits.transactionId " "WHERE kmmTransactions.currencyId = :ID OR kmmSplits.payeeId = :ID " "OR kmmSplits.accountId = :ID OR kmmSplits.costCenterId = :ID"); q.bindValue(":ID", id); if ((!q.exec()) || (!q.next())) { // krazy:exclude=crashy d->buildError(q, Q_FUNC_INFO, "error retrieving reference count"); qFatal("Error retrieving reference count"); // definitely shouldn't happen } return (0 != q.value(0).toULongLong()); } QMap MyMoneyStorageSql::fetchSchedules(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int schedulesNb = (idList.isEmpty() ? d->m_schedules : idList.size()); d->signalProgress(0, schedulesNb, QObject::tr("Loading schedules...")); int progress = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmSchedules"]; QSqlQuery query(*const_cast (this)); QMap sList; //ulong lastId = 0; const MyMoneyDbTable& ts = d->m_db.m_tables["kmmSplits"]; QSqlQuery qs(*const_cast (this)); qs.prepare(ts.selectAllString(false) + " WHERE transactionId = :id ORDER BY splitId;"); QSqlQuery sq(*const_cast (this)); sq.prepare("SELECT payDate from kmmSchedulePaymentHistory where schedId = :id"); QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE"; for (int i = 0; i < idList.count(); ++i) queryString += QString(" id = :id%1 OR").arg(i); queryString = queryString.left(queryString.length() - 2); } queryString += " ORDER BY id"; if (forUpdate) queryString += d->m_driver->forUpdateString(); query.prepare(queryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Schedules"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int typeCol = t.fieldNumber("type"); int occurrenceCol = t.fieldNumber("occurence"); // krazy:exclude=spelling int occurrenceMultiplierCol = t.fieldNumber("occurenceMultiplier"); // krazy:exclude=spelling int paymentTypeCol = t.fieldNumber("paymentType"); int startDateCol = t.fieldNumber("startDate"); int endDateCol = t.fieldNumber("endDate"); int fixedCol = t.fieldNumber("fixed"); int lastDayInMonthCol = t.fieldNumber("lastDayInMonth"); int autoEnterCol = t.fieldNumber("autoEnter"); int lastPaymentCol = t.fieldNumber("lastPayment"); int weekendOptionCol = t.fieldNumber("weekendOption"); int nextPaymentDueCol = t.fieldNumber("nextPaymentDue"); while (query.next()) { MyMoneySchedule s; QString boolChar; QString sId = GETSTRING(idCol); s.setName(GETSTRING(nameCol)); s.setType(static_cast(GETINT(typeCol))); s.setOccurrencePeriod(static_cast(GETINT(occurrenceCol))); s.setOccurrenceMultiplier(GETINT(occurrenceMultiplierCol)); s.setPaymentType(static_cast(GETINT(paymentTypeCol))); s.setStartDate(GETDATE_D(startDateCol)); s.setEndDate(GETDATE_D(endDateCol)); boolChar = GETSTRING(fixedCol); s.setFixed(boolChar == "Y"); boolChar = GETSTRING(lastDayInMonthCol); s.setLastDayInMonth(boolChar == "Y"); boolChar = GETSTRING(autoEnterCol); s.setAutoEnter(boolChar == "Y"); s.setLastPayment(GETDATE_D(lastPaymentCol)); s.setWeekendOption(static_cast(GETINT(weekendOptionCol))); QDate nextPaymentDue = GETDATE_D(nextPaymentDueCol); // convert simple occurrence to compound occurrence int mult = s.occurrenceMultiplier(); Schedule::Occurrence occ = s.occurrencePeriod(); MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); s.setOccurrencePeriod(occ); s.setOccurrenceMultiplier(mult); // now assign the id to the schedule MyMoneySchedule _s(sId, s); s = _s; // read the associated transaction // m_payeeList.clear(); const MyMoneyDbTable& t = d->m_db.m_tables["kmmTransactions"]; QSqlQuery q2(*const_cast (this)); q2.prepare(t.selectAllString(false) + " WHERE id = :id;"); q2.bindValue(":id", s.id()); if (!q2.exec()) throw MYMONEYEXCEPTION(d->buildError(q2, Q_FUNC_INFO, QString("reading Scheduled Transaction"))); // krazy:exclude=crashy QSqlRecord rec = q2.record(); if (!q2.next()) throw MYMONEYEXCEPTION(d->buildError(q2, Q_FUNC_INFO, QString("retrieving scheduled transaction"))); MyMoneyTransaction tx(s.id(), MyMoneyTransaction()); tx.setPostDate(d->GETDATE(q2.value(t.fieldNumber("postDate")).toString())); tx.setMemo(q2.value(t.fieldNumber("memo")).toString()); tx.setEntryDate(d->GETDATE(q2.value(t.fieldNumber("entryDate")).toString())); tx.setCommodity(q2.value(t.fieldNumber("currencyId")).toString()); tx.setBankID(q2.value(t.fieldNumber("bankId")).toString()); qs.bindValue(":id", s.id()); if (!qs.exec()) throw MYMONEYEXCEPTION(d->buildError(qs, Q_FUNC_INFO, "reading Scheduled Splits")); // krazy:exclude=crashy while (qs.next()) { MyMoneySplit sp; d->readSplit(sp, qs); tx.addSplit(sp); } // if (!m_payeeList.isEmpty()) // readPayees(m_payeeList); // Process any key value pair tx.setPairs(d->readKeyValuePairs("TRANSACTION", s.id()).pairs()); // If the transaction doesn't have a post date, setTransaction will reject it. // The old way of handling things was to store the next post date in the schedule object // and set the transaction post date to QDate(). // For compatibility, if this is the case, copy the next post date from the schedule object // to the transaction object post date. if (!tx.postDate().isValid()) { tx.setPostDate(nextPaymentDue); } s.setTransaction(tx); // read in the recorded payments sq.bindValue(":id", s.id()); if (!sq.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading schedule payment history"))); // krazy:exclude=crashy while (sq.next()) s.recordPayment(sq.value(0).toDate()); sList[s.id()] = s; //FIXME: enable when schedules have KVPs. // s.setPairs(readKeyValuePairs("SCHEDULE", s.id()).pairs()); //ulong id = MyMoneyUtils::extractId(s.id().data()); //if(id > lastId) // lastId = id; d->signalProgress(++progress, 0); } return sList; } QMap MyMoneyStorageSql::fetchSchedules() const { return fetchSchedules(QStringList(), false); } QMap MyMoneyStorageSql::fetchSecurities(const QStringList& /*idList*/, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); d->signalProgress(0, d->m_securities, QObject::tr("Loading securities...")); int progress = 0; QMap sList; ulong lastId = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmSecurities"]; QSqlQuery query(*const_cast (this)); query.prepare(t.selectAllString(false) + " ORDER BY id;"); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Securities"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int symbolCol = t.fieldNumber("symbol"); int typeCol = t.fieldNumber("type"); int roundingMethodCol = t.fieldNumber("roundingMethod"); int smallestAccountFractionCol = t.fieldNumber("smallestAccountFraction"); int pricePrecisionCol = t.fieldNumber("pricePrecision"); int tradingCurrencyCol = t.fieldNumber("tradingCurrency"); int tradingMarketCol = t.fieldNumber("tradingMarket"); while (query.next()) { MyMoneySecurity e; QString eid; eid = GETSTRING(idCol); e.setName(GETSTRING(nameCol)); e.setTradingSymbol(GETSTRING(symbolCol)); e.setSecurityType(static_cast(GETINT(typeCol))); e.setRoundingMethod(static_cast(GETINT(roundingMethodCol))); int saf = GETINT(smallestAccountFractionCol); int pp = GETINT(pricePrecisionCol); e.setTradingCurrency(GETSTRING(tradingCurrencyCol)); e.setTradingMarket(GETSTRING(tradingMarketCol)); if (e.tradingCurrency().isEmpty()) e.setTradingCurrency(d->m_storage->pairs()["kmm-baseCurrency"]); if (saf == 0) saf = 100; if (pp == 0 || pp > 10) pp = 4; e.setSmallestAccountFraction(saf); e.setPricePrecision(pp); // Process any key value pairs e.setPairs(d->readKeyValuePairs("SECURITY", eid).pairs()); //tell the storage objects we have a new security object. // FIXME: Adapt to new interface make sure, to take care of the currencies as well // see MyMoneyStorageXML::readSecurites() MyMoneySecurity security(eid, e); sList[security.id()] = security; ulong id = MyMoneyUtils::extractId(security.id()); if (id > lastId) lastId = id; d->signalProgress(++progress, 0); } return sList; } QMap MyMoneyStorageSql::fetchSecurities() const { return fetchSecurities(QStringList(), false); } MyMoneyPrice MyMoneyStorageSql::fetchSinglePrice(const QString& fromId, const QString& toId, const QDate& date_, bool exactDate, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); const MyMoneyDbTable& t = d->m_db.m_tables["kmmPrices"]; static const int priceDateCol = t.fieldNumber("priceDate"); static const int priceCol = t.fieldNumber("price"); static const int priceSourceCol = t.fieldNumber("priceSource"); QSqlQuery query(*const_cast (this)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. // See balance query for why the date logic seems odd. QString queryString = t.selectAllString(false) + " WHERE fromId = :fromId AND toId = :toId AND priceDate < :priceDate "; if (exactDate) queryString += "AND priceDate > :exactDate "; queryString += "ORDER BY priceDate DESC;"; query.prepare(queryString); QDate date(date_); if (!date.isValid()) date = QDate::currentDate(); query.bindValue(":fromId", fromId); query.bindValue(":toId", toId); query.bindValue(":priceDate", date.addDays(1).toString(Qt::ISODate)); if (exactDate) query.bindValue(":exactDate", date.toString(Qt::ISODate)); if (! query.exec()) return MyMoneyPrice(); // krazy:exclude=crashy if (query.next()) { return MyMoneyPrice(fromId, toId, GETDATE_D(priceDateCol), MyMoneyMoney(GETSTRING(priceCol)), GETSTRING(priceSourceCol)); } return MyMoneyPrice(); } MyMoneyPriceList MyMoneyStorageSql::fetchPrices(const QStringList& fromIdList, const QStringList& toIdList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int pricesNb = (fromIdList.isEmpty() ? d->m_prices : fromIdList.size()); d->signalProgress(0, pricesNb, QObject::tr("Loading prices...")); int progress = 0; const_cast (this)->d_func()->m_readingPrices = true; MyMoneyPriceList pList; const MyMoneyDbTable& t = d->m_db.m_tables["kmmPrices"]; QSqlQuery query(*const_cast (this)); QString queryString = t.selectAllString(false); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! fromIdList.empty()) { queryString += " WHERE ("; for (int i = 0; i < fromIdList.count(); ++i) { queryString += QString(" fromId = :fromId%1 OR").arg(i); } queryString = queryString.left(queryString.length() - 2) + ')'; } if (! toIdList.empty()) { queryString += " AND ("; for (int i = 0; i < toIdList.count(); ++i) { queryString += QString(" toId = :toId%1 OR").arg(i); } queryString = queryString.left(queryString.length() - 2) + ')'; } if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (! fromIdList.empty()) { QStringList::ConstIterator bindVal = fromIdList.constBegin(); for (int i = 0; bindVal != fromIdList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":fromId%1").arg(i), *bindVal); } } if (! toIdList.empty()) { QStringList::ConstIterator bindVal = toIdList.constBegin(); for (int i = 0; bindVal != toIdList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":toId%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Prices"))); // krazy:exclude=crashy static const int fromIdCol = t.fieldNumber("fromId"); static const int toIdCol = t.fieldNumber("toId"); static const int priceDateCol = t.fieldNumber("priceDate"); static const int priceCol = t.fieldNumber("price"); static const int priceSourceCol = t.fieldNumber("priceSource"); while (query.next()) { QString from = GETSTRING(fromIdCol); QString to = GETSTRING(toIdCol); QDate date = GETDATE_D(priceDateCol); pList [MyMoneySecurityPair(from, to)].insert(date, MyMoneyPrice(from, to, date, MyMoneyMoney(GETSTRING(priceCol)), GETSTRING(priceSourceCol))); d->signalProgress(++progress, 0); } const_cast (this)->d_func()->m_readingPrices = false; return pList; } MyMoneyPriceList MyMoneyStorageSql::fetchPrices() const { return fetchPrices(QStringList(), QStringList(), false); } QMap MyMoneyStorageSql::fetchCurrencies(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int currenciesNb = (idList.isEmpty() ? d->m_currencies : idList.size()); d->signalProgress(0, currenciesNb, QObject::tr("Loading currencies...")); int progress = 0; QMap cList; const MyMoneyDbTable& t = d->m_db.m_tables["kmmCurrencies"]; QSqlQuery query(*const_cast (this)); QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE"; for (int i = 0; i < idList.count(); ++i) queryString += QString(" isocode = :id%1 OR").arg(i); queryString = queryString.left(queryString.length() - 2); } queryString += " ORDER BY ISOcode"; if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Currencies"))); // krazy:exclude=crashy int ISOcodeCol = t.fieldNumber("ISOcode"); int nameCol = t.fieldNumber("name"); int typeCol = t.fieldNumber("type"); int symbol1Col = t.fieldNumber("symbol1"); int symbol2Col = t.fieldNumber("symbol2"); int symbol3Col = t.fieldNumber("symbol3"); int smallestCashFractionCol = t.fieldNumber("smallestCashFraction"); int smallestAccountFractionCol = t.fieldNumber("smallestAccountFraction"); int pricePrecisionCol = t.fieldNumber("pricePrecision"); while (query.next()) { QString id; MyMoneySecurity c; QChar symbol[3]; id = GETSTRING(ISOcodeCol); c.setName(GETSTRING(nameCol)); c.setSecurityType(static_cast(GETINT(typeCol))); symbol[0] = QChar(GETINT(symbol1Col)); symbol[1] = QChar(GETINT(symbol2Col)); symbol[2] = QChar(GETINT(symbol3Col)); c.setSmallestCashFraction(GETINT(smallestCashFractionCol)); c.setSmallestAccountFraction(GETINT(smallestAccountFractionCol)); c.setPricePrecision(GETINT(pricePrecisionCol)); c.setTradingSymbol(QString(symbol, 3).trimmed()); cList[id] = MyMoneySecurity(id, c); d->signalProgress(++progress, 0); } return cList; } QMap MyMoneyStorageSql::fetchCurrencies() const { return fetchCurrencies(QStringList(), false); } QMap MyMoneyStorageSql::fetchReports(const QStringList& /*idList*/, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); d->signalProgress(0, d->m_reports, QObject::tr("Loading reports...")); int progress = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmReportConfig"]; QSqlQuery query(*const_cast (this)); query.prepare(t.selectAllString(true)); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading reports"))); // krazy:exclude=crashy int xmlCol = t.fieldNumber("XML"); QMap rList; while (query.next()) { QDomDocument dom; dom.setContent(GETSTRING(xmlCol), false); QDomNode child = dom.firstChild(); child = child.firstChild(); MyMoneyReport report; if (report.read(child.toElement())) rList[report.id()] = report; d->signalProgress(++progress, 0); } return rList; } QMap MyMoneyStorageSql::fetchReports() const { return fetchReports(QStringList(), false); } QMap MyMoneyStorageSql::fetchBudgets(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int budgetsNb = (idList.isEmpty() ? d->m_budgets : idList.size()); d->signalProgress(0, budgetsNb, QObject::tr("Loading budgets...")); int progress = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmBudgetConfig"]; QSqlQuery query(*const_cast (this)); QString queryString(t.selectAllString(false)); if (! idList.empty()) { queryString += " WHERE id = '" + idList.join("' OR id = '") + '\''; } if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading budgets"))); // krazy:exclude=crashy QMap budgets; int xmlCol = t.fieldNumber("XML"); while (query.next()) { QDomDocument dom; dom.setContent(GETSTRING(xmlCol), false); QDomNode child = dom.firstChild(); child = child.firstChild(); MyMoneyBudget budget(child.toElement()); budgets.insert(budget.id(), budget); d->signalProgress(++progress, 0); } return budgets; } QMap MyMoneyStorageSql::fetchBudgets() const { return fetchBudgets(QStringList(), false); } ulong MyMoneyStorageSql::getNextBudgetId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdBudgets>(QLatin1String("kmmBudgetConfig"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextAccountId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdAccounts>(QLatin1String("kmmAccounts"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextInstitutionId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdInstitutions>(QLatin1String("kmmInstitutions"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextPayeeId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdPayees>(QLatin1String("kmmPayees"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextTagId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdTags>(QLatin1String("kmmTags"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextReportId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdReports>(QLatin1String("kmmReportConfig"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextScheduleId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdSchedules>(QLatin1String("kmmSchedules"), QLatin1String("id"), 3); } ulong MyMoneyStorageSql::getNextSecurityId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdSecurities>(QLatin1String("kmmSecurities"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextTransactionId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdTransactions>(QLatin1String("kmmTransactions"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextOnlineJobId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdOnlineJobs>(QLatin1String("kmmOnlineJobs"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextPayeeIdentifierId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdPayeeIdentifier>(QLatin1String("kmmPayeeIdentifier"), QLatin1String("id"), 5); } ulong MyMoneyStorageSql::getNextCostCenterId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdCostCenter>(QLatin1String("kmmCostCenterIdentifier"), QLatin1String("id"), 5); } ulong MyMoneyStorageSql::incrementBudgetId() { Q_D(MyMoneyStorageSql); d->m_hiIdBudgets = getNextBudgetId() + 1; return (d->m_hiIdBudgets - 1); } /** * @warning This method uses getNextAccountId() internaly. The database is not informed which can cause issues * when the database is accessed concurrently. Then maybe a single id is used twice but the RDBMS will detect the * issue and KMyMoney crashes. This issue can only occour when two instances of KMyMoney access the same database. * But in this unlikley case MyMoneyStorageSql will have a lot more issues, I think. */ ulong MyMoneyStorageSql::incrementAccountId() { Q_D(MyMoneyStorageSql); d->m_hiIdAccounts = getNextAccountId() + 1; return (d->m_hiIdAccounts - 1); } ulong MyMoneyStorageSql::incrementInstitutionId() { Q_D(MyMoneyStorageSql); d->m_hiIdInstitutions = getNextInstitutionId() + 1; return (d->m_hiIdInstitutions - 1); } ulong MyMoneyStorageSql::incrementPayeeId() { Q_D(MyMoneyStorageSql); d->m_hiIdPayees = getNextPayeeId() + 1; return (d->m_hiIdPayees - 1); } ulong MyMoneyStorageSql::incrementTagId() { Q_D(MyMoneyStorageSql); d->m_hiIdTags = getNextTagId() + 1; return (d->m_hiIdTags - 1); } ulong MyMoneyStorageSql::incrementReportId() { Q_D(MyMoneyStorageSql); d->m_hiIdReports = getNextReportId() + 1; return (d->m_hiIdReports - 1); } ulong MyMoneyStorageSql::incrementScheduleId() { Q_D(MyMoneyStorageSql); d->m_hiIdSchedules = getNextScheduleId() + 1; return (d->m_hiIdSchedules - 1); } ulong MyMoneyStorageSql::incrementSecurityId() { Q_D(MyMoneyStorageSql); d->m_hiIdSecurities = getNextSecurityId() + 1; return (d->m_hiIdSecurities - 1); } ulong MyMoneyStorageSql::incrementTransactionId() { Q_D(MyMoneyStorageSql); d->m_hiIdTransactions = getNextTransactionId() + 1; return (d->m_hiIdTransactions - 1); } ulong MyMoneyStorageSql::incrementOnlineJobId() { Q_D(MyMoneyStorageSql); d->m_hiIdOnlineJobs = getNextOnlineJobId() + 1; return (d->m_hiIdOnlineJobs - 1); } ulong MyMoneyStorageSql::incrementPayeeIdentfierId() { Q_D(MyMoneyStorageSql); d->m_hiIdPayeeIdentifier = getNextPayeeIdentifierId() + 1; return (d->m_hiIdPayeeIdentifier - 1); } ulong MyMoneyStorageSql::incrementCostCenterId() { Q_D(MyMoneyStorageSql); d->m_hiIdCostCenter = getNextCostCenterId() + 1; return (d->m_hiIdCostCenter - 1); } void MyMoneyStorageSql::loadAccountId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdAccounts = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadTransactionId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdTransactions = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadPayeeId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdPayees = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadTagId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdTags = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadInstitutionId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdInstitutions = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadScheduleId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdSchedules = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadSecurityId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdSecurities = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadReportId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdReports = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadBudgetId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdBudgets = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadOnlineJobId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdOnlineJobs = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadPayeeIdentifierId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdPayeeIdentifier = id; d->writeFileInfo(); } //**************************************************** void MyMoneyStorageSql::setProgressCallback(void(*callback)(int, int, const QString&)) { Q_D(MyMoneyStorageSql); d->m_progressCallback = callback; } -void MyMoneyStorageSql::readFile(QIODevice* s, IMyMoneySerialize* storage) +void MyMoneyStorageSql::readFile(QIODevice* s, MyMoneyStorageMgr* storage) { Q_UNUSED(s); Q_UNUSED(storage) } -void MyMoneyStorageSql::writeFile(QIODevice* s, IMyMoneySerialize* storage) +void MyMoneyStorageSql::writeFile(QIODevice* s, MyMoneyStorageMgr* storage) { Q_UNUSED(s); Q_UNUSED(storage) } // **************************** Error display routine ******************************* QDate MyMoneyStorageSqlPrivate::m_startDate = QDate(1900, 1, 1); void MyMoneyStorageSql::setStartDate(const QDate& startDate) { MyMoneyStorageSqlPrivate::m_startDate = startDate; } QMap< QString, MyMoneyCostCenter > MyMoneyStorageSql::fetchCostCenters(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); Q_UNUSED(forUpdate); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) { int costCenterNb = (idList.isEmpty() ? 100 : idList.size()); d->signalProgress(0, costCenterNb, QObject::tr("Loading cost center...")); } int progress = 0; QMap costCenterList; //ulong lastId; const MyMoneyDbTable& t = d->m_db.m_tables["kmmCostCenter"]; QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare(t.selectAllString()); } else { QString whereClause = " where ("; QString itemConnector = ""; foreach (const QString& it, idList) { whereClause.append(QString("%1id = '%2'").arg(itemConnector).arg(it)); itemConnector = " or "; } whereClause += ')'; query.prepare(t.selectAllString(false) + whereClause); } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading CostCenter"))); // krazy:exclude=crashy const int idCol = t.fieldNumber("id"); const int nameCol = t.fieldNumber("name"); while (query.next()) { MyMoneyCostCenter costCenter; QString pid = GETSTRING(idCol); costCenter.setName(GETSTRING(nameCol)); costCenterList[pid] = MyMoneyCostCenter(pid, costCenter); if (d->m_displayStatus) d->signalProgress(++progress, 0); } return costCenterList; } QMap< QString, MyMoneyCostCenter > MyMoneyStorageSql::fetchCostCenters() const { return fetchCostCenters(QStringList(), false); } diff --git a/kmymoney/mymoney/storage/mymoneystoragesql.h b/kmymoney/mymoney/storage/mymoneystoragesql.h index fb28bc244..ddc4438e3 100644 --- a/kmymoney/mymoney/storage/mymoneystoragesql.h +++ b/kmymoney/mymoney/storage/mymoneystoragesql.h @@ -1,323 +1,322 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2005 Tony Bloomfield * Copyright (C) Fernando Vilas * Copyright (C) 2014 Christian Dávid * (C) 2017 by Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef MYMONEYSTORAGESQL_H #define MYMONEYSTORAGESQL_H #include #include #include "imymoneystorageformat.h" #include "mymoneyunittestable.h" // This is a convenience functor to make it easier to use STL algorithms // It will return false if the MyMoneyTransaction DOES match the filter. // This functor may disappear when all filtering can be handled in SQL. class QUrl; class QDate; class QIODevice; -class IMyMoneyStorage; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneyCostCenter; class MyMoneyMoney; class MyMoneySchedule; class MyMoneyPayee; class MyMoneyTag; class MyMoneySplit; class MyMoneyTransaction; class MyMoneyTransactionFilter; class MyMoneyBudget; class MyMoneyReport; class MyMoneyPrice; class payeeIdentifier; class onlineJob; class databaseStoreableObject; class MyMoneyStorageSql; -class IMyMoneySerialize; +class MyMoneyStorageMgr; template struct QPair; template class QMap; typedef QPair MyMoneySecurityPair; typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; namespace eMyMoney { namespace TransactionFilter { enum class State; } } /** * The MyMoneyDbColumn class is a base type for generic db columns. * Derived types exist for several common column types. * * @todo Remove unneeded columns which store the row count of tables from kmmFileInfo */ class MyMoneyStorageSqlPrivate; -class MyMoneyStorageSql : public IMyMoneyStorageFormat, public QSqlDatabase, public QSharedData +class MyMoneyStorageSql : public IMyMoneyOperationsFormat, public QSqlDatabase, public QSharedData { Q_DISABLE_COPY(MyMoneyStorageSql) friend class MyMoneyDbDef; KMM_MYMONEY_UNIT_TESTABLE public: - explicit MyMoneyStorageSql(IMyMoneySerialize *storage, const QUrl&); + explicit MyMoneyStorageSql(MyMoneyStorageMgr *storage, const QUrl&); ~MyMoneyStorageSql() override; uint currentVersion() const; /** * MyMoneyStorageSql - open database file * * @param url pseudo-URL of database to be opened * @param openMode open mode, same as for QFile::open * @param clear whether existing data can be deleted * @return 0 - database successfully opened * @return 1 - database not opened, use lastError function for reason * @return -1 - output database not opened, contains data, clean not specified * */ int open(const QUrl &url, int openMode, bool clear = false); /** * MyMoneyStorageSql close the database * * @return void * */ void close(bool logoff = true); /** * MyMoneyStorageSql read all the database into storage * * @return void * */ bool readFile(); /** * MyMoneyStorageSql write/update the database from storage * * @return void * */ bool writeFile(); /** * MyMoneyStorageSql generalized error routine * * @return : error message to be displayed * */ - QString lastError() const; + QString lastError() const; /** * This method is used when a database file is open, and the data is to * be saved in a different file or format. It will ensure that all data * from the database is available in memory to enable it to be written. */ virtual void fillStorage(); /** * The following functions correspond to the identically named (usually) functions * within the Storage Manager, and are called to update the database */ void modifyUserInfo(const MyMoneyPayee& payee); void addInstitution(const MyMoneyInstitution& inst); void modifyInstitution(const MyMoneyInstitution& inst); void removeInstitution(const MyMoneyInstitution& inst); void addPayee(const MyMoneyPayee& payee); void modifyPayee(MyMoneyPayee payee); void removePayee(const MyMoneyPayee& payee); void addTag(const MyMoneyTag& tag); void modifyTag(const MyMoneyTag& tag); void removeTag(const MyMoneyTag& tag); void addAccount(const MyMoneyAccount& acc); void modifyAccount(const MyMoneyAccount& acc); void removeAccount(const MyMoneyAccount& acc); void addTransaction(const MyMoneyTransaction& tx); void modifyTransaction(const MyMoneyTransaction& tx); void removeTransaction(const MyMoneyTransaction& tx); void addSchedule(const MyMoneySchedule& sch); void modifySchedule(const MyMoneySchedule& sch); void removeSchedule(const MyMoneySchedule& sch); void addSecurity(const MyMoneySecurity& sec); void modifySecurity(const MyMoneySecurity& sec); void removeSecurity(const MyMoneySecurity& sec); void addPrice(const MyMoneyPrice& p); void removePrice(const MyMoneyPrice& p); void addCurrency(const MyMoneySecurity& sec); void modifyCurrency(const MyMoneySecurity& sec); void removeCurrency(const MyMoneySecurity& sec); void addReport(const MyMoneyReport& rep); void modifyReport(const MyMoneyReport& rep); void removeReport(const MyMoneyReport& rep); void addBudget(const MyMoneyBudget& bud); void modifyBudget(const MyMoneyBudget& bud); void removeBudget(const MyMoneyBudget& bud); void addOnlineJob(const onlineJob& job); void modifyOnlineJob(const onlineJob& job); void removeOnlineJob(const onlineJob& job); void addPayeeIdentifier(payeeIdentifier& ident); void modifyPayeeIdentifier(const payeeIdentifier& ident); void removePayeeIdentifier(const payeeIdentifier& ident); ulong transactionCount(const QString& aid) const; QHash transactionCountMap() const; /** * The following functions are perform the same operations as the * above functions, but on a QList of the items. * This reduces db round-trips, so should be the preferred method when * such a function exists. */ void modifyAccountList(const QList& acc); /** * the storage manager also needs the following read entry points */ QMap fetchAccounts(const QStringList& idList, bool forUpdate = false) const; QMap fetchAccounts() const; QMap fetchBalance(const QStringList& id, const QDate& date) const; QMap fetchBudgets(const QStringList& idList, bool forUpdate = false) const; QMap fetchBudgets() const; QMap fetchCurrencies(const QStringList& idList, bool forUpdate = false) const; QMap fetchCurrencies() const; QMap fetchInstitutions(const QStringList& idList, bool forUpdate = false) const; QMap fetchInstitutions() const; QMap fetchPayees(const QStringList& idList, bool forUpdate = false) const; QMap fetchPayees() const; QMap fetchTags(const QStringList& idList, bool forUpdate = false) const; QMap fetchTags() const; QMap fetchOnlineJobs(const QStringList& idList, bool forUpdate = false) const; QMap fetchOnlineJobs() const; QMap fetchCostCenters(const QStringList& idList, bool forUpdate = false) const; QMap fetchCostCenters() const; MyMoneyPriceList fetchPrices(const QStringList& fromIdList, const QStringList& toIdList, bool forUpdate = false) const; MyMoneyPriceList fetchPrices() const; MyMoneyPrice fetchSinglePrice(const QString& fromId, const QString& toId, const QDate& date_, bool exactDate, bool = false) const; QMap fetchReports(const QStringList& idList, bool forUpdate = false) const; QMap fetchReports() const; QMap fetchSchedules(const QStringList& idList, bool forUpdate = false) const; QMap fetchSchedules() const; QMap fetchSecurities(const QStringList& idList, bool forUpdate = false) const; QMap fetchSecurities() const; QMap fetchTransactions(const QString& tidList, const QString& dateClause, bool forUpdate = false) const; QMap fetchTransactions(const QString& tidList) const; QMap fetchTransactions() const; QMap fetchTransactions(const MyMoneyTransactionFilter& filter) const; payeeIdentifier fetchPayeeIdentifier(const QString& id) const; QMap fetchPayeeIdentifiers(const QStringList& idList) const; QMap fetchPayeeIdentifiers() const; bool isReferencedByTransaction(const QString& id) const; void readPayees(const QString&); void readPayees(const QList& payeeList); void readPayees(); void readTags(const QString&); void readTags(const QList& tagList); void readTags(); void readTransactions(const MyMoneyTransactionFilter& filter); void setProgressCallback(void(*callback)(int, int, const QString&)) override; - void readFile(QIODevice* s, IMyMoneySerialize* storage) override; - void writeFile(QIODevice* s, IMyMoneySerialize* storage) override; + void readFile(QIODevice* s, MyMoneyStorageMgr* storage) override; + void writeFile(QIODevice* s, MyMoneyStorageMgr* storage) override; void startCommitUnit(const QString& callingFunction); bool endCommitUnit(const QString& callingFunction); void cancelCommitUnit(const QString& callingFunction); ulong getRecCount(const QString& table) const; ulong getNextBudgetId() const; ulong getNextAccountId() const; ulong getNextInstitutionId() const; ulong getNextPayeeId() const; ulong getNextTagId() const; ulong getNextOnlineJobId() const; ulong getNextPayeeIdentifierId() const; ulong getNextReportId() const; ulong getNextScheduleId() const; ulong getNextSecurityId() const; ulong getNextTransactionId() const; ulong getNextCostCenterId() const; ulong incrementBudgetId(); ulong incrementAccountId(); ulong incrementInstitutionId(); ulong incrementPayeeId(); ulong incrementTagId(); ulong incrementReportId(); ulong incrementScheduleId(); ulong incrementSecurityId(); ulong incrementTransactionId(); ulong incrementOnlineJobId(); ulong incrementPayeeIdentfierId(); ulong incrementCostCenterId(); void loadAccountId(ulong id); void loadTransactionId(ulong id); void loadPayeeId(ulong id); void loadTagId(ulong id); void loadInstitutionId(ulong id); void loadScheduleId(ulong id); void loadSecurityId(ulong id); void loadReportId(ulong id); void loadBudgetId(ulong id); void loadOnlineJobId(ulong id); void loadPayeeIdentifierId(ulong id); void loadCostCenterId(ulong id); /** * This method allows to modify the precision with which prices * are handled within the object. The default of the precision is 4. */ static void setPrecision(int prec); /** * This method allows to modify the start date for transaction retrieval * The default of the precision is Jan 1st, 1900. */ static void setStartDate(const QDate &startDate); private: MyMoneyStorageSqlPrivate* const d_ptr; Q_DECLARE_PRIVATE(MyMoneyStorageSql) }; #endif // MYMONEYSTORAGESQL_H diff --git a/kmymoney/mymoney/storage/mymoneystoragesql_p.h b/kmymoney/mymoney/storage/mymoneystoragesql_p.h index 0b20deeb1..07de4a9ed 100644 --- a/kmymoney/mymoney/storage/mymoneystoragesql_p.h +++ b/kmymoney/mymoney/storage/mymoneystoragesql_p.h @@ -1,2824 +1,2815 @@ /*************************************************************************** mymoneystoragesql.cpp --------------------- begin : 11 November 2005 copyright : (C) 2005 by Tony Bloomfield email : tonybloom@users.sourceforge.net : Fernando Vilas : Christian Dávid (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGESQL_P_H #define MYMONEYSTORAGESQL_P_H #include "mymoneystoragesql.h" // ---------------------------------------------------------------------------- // System Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // TODO: port KF5 (needed for payeeidentifier plugin) //#include // ---------------------------------------------------------------------------- // Project Includes -#include "imymoneystorage.h" -#include "imymoneyserialize.h" +#include "mymoneystoragemgr.h" #include "kmymoneystorageplugin.h" #include "onlinejobadministration.h" #include "payeeidentifier/payeeidentifierloader.h" #include "onlinetasks/interfaces/tasks/onlinetask.h" #include "mymoneycostcenter.h" #include "mymoneyexception.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneymoney.h" #include "mymoneyschedule.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneybudget.h" #include "mymoneyreport.h" #include "mymoneyprice.h" #include "mymoneyutils.h" #include "mymoneydbdef.h" #include "mymoneydbdriver.h" #include "payeeidentifier/payeeidentifierdata.h" #include "mymoneyenums.h" #include "mymoneystoragenames.h" using namespace eMyMoney; using namespace MyMoneyStandardAccounts; class FilterFail { public: explicit FilterFail(const MyMoneyTransactionFilter& filter) : m_filter(filter) {} inline bool operator()(const QPair& transactionPair) { return (*this)(transactionPair.second); } inline bool operator()(const MyMoneyTransaction& transaction) { return !m_filter.match(transaction); } private: MyMoneyTransactionFilter m_filter; }; //***************************************************************************** // Create a class to handle db transactions using scope // // Don't let the database object get destroyed while this object exists, // that would result in undefined behavior. class MyMoneyDbTransaction { public: explicit MyMoneyDbTransaction(MyMoneyStorageSql& db, const QString& name) : m_db(db), m_name(name) { db.startCommitUnit(name); } ~MyMoneyDbTransaction() { if (std::uncaught_exception()) { m_db.cancelCommitUnit(m_name); } else { m_db.endCommitUnit(m_name); } } private: MyMoneyStorageSql& m_db; QString m_name; }; /** * The MyMoneySqlQuery class is derived from QSqlQuery to provide * a way to adjust some queries based on database type and make * debugging easier by providing a place to put debug statements. */ class MyMoneySqlQuery : public QSqlQuery { public: explicit MyMoneySqlQuery(MyMoneyStorageSql* db = 0) : QSqlQuery(*db) { } virtual ~MyMoneySqlQuery() { } bool exec() { qDebug() << "start sql:" << lastQuery(); bool rc = QSqlQuery::exec(); qDebug() << "end sql:" << QSqlQuery::executedQuery(); qDebug() << "***Query returned:" << rc << ", row count:" << numRowsAffected(); return (rc); } bool exec(const QString & query) { qDebug() << "start sql:" << query; bool rc = QSqlQuery::exec(query); qDebug() << "end sql:" << QSqlQuery::executedQuery(); qDebug() << "***Query returned:" << rc << ", row count:" << numRowsAffected(); return rc; } bool prepare(const QString & query) { return (QSqlQuery::prepare(query)); } }; #define GETSTRING(a) query.value(a).toString() #define GETDATE(a) getDate(GETSTRING(a)) #define GETDATE_D(a) d->getDate(GETSTRING(a)) #define GETDATETIME(a) getDateTime(GETSTRING(a)) #define GETINT(a) query.value(a).toInt() #define GETULL(a) query.value(a).toULongLong() class MyMoneyStorageSqlPrivate { Q_DISABLE_COPY(MyMoneyStorageSqlPrivate) Q_DECLARE_PUBLIC(MyMoneyStorageSql) public: explicit MyMoneyStorageSqlPrivate(MyMoneyStorageSql* qq) : q_ptr(qq), m_dbVersion(0), m_loadAll(false), m_override(false), m_hiIdInstitutions(0), m_hiIdPayees(0), m_hiIdTags(0), m_hiIdAccounts(0), m_hiIdTransactions(0), m_hiIdSchedules(0), m_hiIdSecurities(0), m_hiIdReports(0), m_hiIdBudgets(0), m_hiIdOnlineJobs(0), m_hiIdPayeeIdentifier(0), m_displayStatus(false), m_readingPrices(false), m_newDatabase(false), m_progressCallback(nullptr) { m_preferred.setReportAllSplits(false); } ~MyMoneyStorageSqlPrivate() { } /** * MyMoneyStorageSql get highest ID number from the database * * @return : highest ID number */ ulong highestNumberFromIdString(QString tableName, QString tableField, int prefixLength) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); if (!query.exec(m_driver->highestNumberFromIdString(tableName, tableField, prefixLength)) || !query.next()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("retrieving highest ID number"))); // krazy:exclude=crashy return query.value(0).toULongLong(); } /** * @name writeFromStorageMethods * @{ * These method write all data from m_storage to the database. Data which is * stored in the database is deleted. */ void writeUserInformation(); void writeInstitutions() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database // anything not in the list needs to be inserted // anything which is will be updated and removed from the list // anything left over at the end will need to be deleted // this is an expensive and inconvenient way to do things; find a better way // one way would be to build the lists when reading the db // unfortunately this object does not persist between read and write // it would also be nice if we could tell which objects had been updated since we read them in QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmInstitutions;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Institution list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const QList list = m_storage->institutionList(); QList insertList; QList updateList; QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmInstitutions"].updateString()); query2.prepare(m_db.m_tables["kmmInstitutions"].insertString()); signalProgress(0, list.count(), "Writing Institutions..."); foreach (const MyMoneyInstitution& i, list) { if (dbList.contains(i.id())) { dbList.removeAll(i.id()); updateList << i; } else { insertList << i; } signalProgress(++m_institutions, 0); } if (!insertList.isEmpty()) writeInstitutionList(insertList, query2); if (!updateList.isEmpty()) writeInstitutionList(updateList, query); if (!dbList.isEmpty()) { QVariantList deleteList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { deleteList << it; } query.prepare("DELETE FROM kmmInstitutions WHERE id = :id"); query.bindValue(":id", deleteList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Institution")); deleteKeyValuePairs("OFXSETTINGS", deleteList); } } void writePayees() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QSqlQuery query(*q); query.prepare("SELECT id FROM kmmPayees;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Payee list")); // krazy:exclude=crashy QList dbList; dbList.reserve(query.numRowsAffected()); while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->payeeList(); MyMoneyPayee user(QString("USER"), m_storage->user()); list.prepend(user); signalProgress(0, list.count(), "Writing Payees..."); Q_FOREACH(const MyMoneyPayee& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); q->modifyPayee(it); } else { q->addPayee(it); } signalProgress(++m_payees, 0); } if (!dbList.isEmpty()) { QMap payeesToDelete = q->fetchPayees(dbList, true); Q_FOREACH(const MyMoneyPayee& payee, payeesToDelete) { q->removePayee(payee); } } } void writeTags() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmTags;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Tag list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->tagList(); signalProgress(0, list.count(), "Writing Tags..."); QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmTags"].updateString()); query2.prepare(m_db.m_tables["kmmTags"].insertString()); foreach (const MyMoneyTag& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeTag(it, query); } else { writeTag(it, query2); } signalProgress(++m_tags, 0); } if (!dbList.isEmpty()) { QVariantList deleteList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { deleteList << it; } query.prepare(m_db.m_tables["kmmTags"].deleteString()); query.bindValue(":id", deleteList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Tag")); m_tags -= query.numRowsAffected(); } } void writeAccounts() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmAccounts;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Account list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list; m_storage->accountList(list); unsigned progress = 0; signalProgress(0, list.count(), "Writing Accounts..."); if (dbList.isEmpty()) { // new table, insert standard accounts query.prepare(m_db.m_tables["kmmAccounts"].insertString()); } else { query.prepare(m_db.m_tables["kmmAccounts"].updateString()); } // Attempt to write the standard accounts. For an empty db, this will fail. try { QList stdList; stdList << m_storage->asset(); stdList << m_storage->liability(); stdList << m_storage->expense(); stdList << m_storage->income(); stdList << m_storage->equity(); writeAccountList(stdList, query); m_accounts += stdList.size(); } catch (const MyMoneyException &) { // If the above failed, assume that the database is empty and create // the standard accounts by hand before writing them. MyMoneyAccount acc_l; acc_l.setAccountType(Account::Type::Liability); acc_l.setName("Liability"); MyMoneyAccount liability(stdAccNames[stdAccLiability], acc_l); MyMoneyAccount acc_a; acc_a.setAccountType(Account::Type::Asset); acc_a.setName("Asset"); MyMoneyAccount asset(stdAccNames[stdAccAsset], acc_a); MyMoneyAccount acc_e; acc_e.setAccountType(Account::Type::Expense); acc_e.setName("Expense"); MyMoneyAccount expense(stdAccNames[stdAccExpense], acc_e); MyMoneyAccount acc_i; acc_i.setAccountType(Account::Type::Income); acc_i.setName("Income"); MyMoneyAccount income(stdAccNames[stdAccIncome], acc_i); MyMoneyAccount acc_q; acc_q.setAccountType(Account::Type::Equity); acc_q.setName("Equity"); MyMoneyAccount equity(stdAccNames[stdAccEquity], acc_q); QList stdList; stdList << asset; stdList << liability; stdList << expense; stdList << income; stdList << equity; writeAccountList(stdList, query); m_accounts += stdList.size(); } QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmAccounts"].updateString()); query2.prepare(m_db.m_tables["kmmAccounts"].insertString()); QList updateList; QList insertList; // Update the accounts that exist; insert the ones that do not. foreach (const MyMoneyAccount& it, list) { - m_transactionCountMap[it.id()] = m_storagePtr->transactionCount(it.id()); + m_transactionCountMap[it.id()] = m_storage->transactionCount(it.id()); if (dbList.contains(it.id())) { dbList.removeAll(it.id()); updateList << it; } else { insertList << it; } signalProgress(++progress, 0); ++m_accounts; } writeAccountList(updateList, query); writeAccountList(insertList, query2); // Delete the accounts that are in the db but no longer in memory. if (!dbList.isEmpty()) { QVariantList kvpList; query.prepare("DELETE FROM kmmAccounts WHERE id = :id"); foreach (const QString& it, dbList) { - if (!m_storagePtr->isStandardAccount(it)) { + if (!m_storage->isStandardAccount(it)) { kvpList << it; } } query.bindValue(":id", kvpList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Account")); deleteKeyValuePairs("ACCOUNT", kvpList); deleteKeyValuePairs("ONLINEBANKING", kvpList); } } void writeTransactions() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmTransactions WHERE txType = 'N';"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Transaction list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList list; m_storage->transactionList(list, filter); signalProgress(0, list.count(), "Writing Transactions..."); QList::ConstIterator it; QSqlQuery q2(*q); query.prepare(m_db.m_tables["kmmTransactions"].updateString()); q2.prepare(m_db.m_tables["kmmTransactions"].insertString()); foreach (const MyMoneyTransaction& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeTransaction(it.id(), it, query, "N"); } else { writeTransaction(it.id(), it, q2, "N"); } signalProgress(++m_transactions, 0); } if (!dbList.isEmpty()) { foreach (const QString& it, dbList) { deleteTransaction(it); } } } void writeSchedules() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); query.prepare("SELECT id FROM kmmSchedules;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Schedule list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const auto list = m_storage->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QSqlQuery query2(*q); //TODO: find a way to prepare the queries outside of the loop. writeSchedule() // modifies the query passed to it, so they have to be re-prepared every pass. signalProgress(0, list.count(), "Writing Schedules..."); foreach (const MyMoneySchedule& it, list) { query.prepare(m_db.m_tables["kmmSchedules"].updateString()); query2.prepare(m_db.m_tables["kmmSchedules"].insertString()); bool insert = true; if (dbList.contains(it.id())) { dbList.removeAll(it.id()); insert = false; writeSchedule(it, query, insert); } else { writeSchedule(it, query2, insert); } signalProgress(++m_schedules, 0); } if (!dbList.isEmpty()) { foreach (const QString& it, dbList) { deleteSchedule(it); } } } void writeSecurities() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT id FROM kmmSecurities;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building security list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const QList securityList = m_storage->securityList(); signalProgress(0, securityList.count(), "Writing Securities..."); query.prepare(m_db.m_tables["kmmSecurities"].updateString()); query2.prepare(m_db.m_tables["kmmSecurities"].insertString()); foreach (const MyMoneySecurity& it, securityList) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeSecurity(it, query); } else { writeSecurity(it, query2); } signalProgress(++m_securities, 0); } if (!dbList.isEmpty()) { QVariantList idList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { idList << it; } query.prepare("DELETE FROM kmmSecurities WHERE id = :id"); query2.prepare("DELETE FROM kmmPrices WHERE fromId = :id OR toId = :id"); query.bindValue(":id", idList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Security")); query2.bindValue(":fromId", idList); query2.bindValue(":toId", idList); if (!query2.execBatch()) throw MYMONEYEXCEPTION(buildError(query2, Q_FUNC_INFO, "deleting Security")); deleteKeyValuePairs("SECURITY", idList); } } void writePrices() { Q_Q(MyMoneyStorageSql); // due to difficulties in matching and determining deletes // easiest way is to delete all and re-insert QSqlQuery query(*q); query.prepare("DELETE FROM kmmPrices"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("deleting Prices"))); // krazy:exclude=crashy m_prices = 0; const MyMoneyPriceList list = m_storage->priceList(); signalProgress(0, list.count(), "Writing Prices..."); MyMoneyPriceList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { writePricePair(*it); } } void writeCurrencies() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT ISOCode FROM kmmCurrencies;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Currency list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); const QList currencyList = m_storage->currencyList(); signalProgress(0, currencyList.count(), "Writing Currencies..."); query.prepare(m_db.m_tables["kmmCurrencies"].updateString()); query2.prepare(m_db.m_tables["kmmCurrencies"].insertString()); foreach (const MyMoneySecurity& it, currencyList) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeCurrency(it, query); } else { writeCurrency(it, query2); } signalProgress(++m_currencies, 0); } if (!dbList.isEmpty()) { QVariantList isoCodeList; query.prepare("DELETE FROM kmmCurrencies WHERE ISOCode = :ISOCode"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { isoCodeList << it; } query.bindValue(":ISOCode", isoCodeList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Currency")); } } void writeFileInfo() { Q_Q(MyMoneyStorageSql); // we have no real way of knowing when these change, so re-write them every time QVariantList kvpList; kvpList << ""; QList > pairs; pairs << m_storage->pairs(); deleteKeyValuePairs("STORAGE", kvpList); writeKeyValuePairs("STORAGE", kvpList, pairs); QSqlQuery query(*q); query.prepare("SELECT count(*) FROM kmmFileInfo;"); if (!query.exec() || !query.next()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "checking fileinfo")); // krazy:exclude=crashy if (query.value(0).toInt() == 0) { // Cannot use "INSERT INTO kmmFileInfo DEFAULT VALUES;" because it is not supported by MySQL query.prepare(QLatin1String("INSERT INTO kmmFileInfo (version) VALUES (null);")); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "inserting fileinfo")); // krazy:exclude=crashy } query.prepare(QLatin1String( "UPDATE kmmFileInfo SET " "version = :version, " "fixLevel = :fixLevel, " "created = :created, " "lastModified = :lastModified, " "baseCurrency = :baseCurrency, " "dateRangeStart = :dateRangeStart, " "dateRangeEnd = :dateRangeEnd, " "hiInstitutionId = :hiInstitutionId, " "hiPayeeId = :hiPayeeId, " "hiTagId = :hiTagId, " "hiAccountId = :hiAccountId, " "hiTransactionId = :hiTransactionId, " "hiScheduleId = :hiScheduleId, " "hiSecurityId = :hiSecurityId, " "hiReportId = :hiReportId, " "hiBudgetId = :hiBudgetId, " "hiOnlineJobId = :hiOnlineJobId, " "hiPayeeIdentifierId = :hiPayeeIdentifierId, " "encryptData = :encryptData, " "updateInProgress = :updateInProgress, " "logonUser = :logonUser, " "logonAt = :logonAt, " //! @todo The following updates are for backwards compatibility only //! remove backwards compatibility in a later version "institutions = :institutions, " "accounts = :accounts, " "payees = :payees, " "tags = :tags, " "transactions = :transactions, " "splits = :splits, " "securities = :securities, " "prices = :prices, " "currencies = :currencies, " "schedules = :schedules, " "reports = :reports, " "kvps = :kvps, " "budgets = :budgets; " ) ); query.bindValue(":version", m_dbVersion); query.bindValue(":fixLevel", m_storage->fileFixVersion()); query.bindValue(":created", m_storage->creationDate().toString(Qt::ISODate)); //q.bindValue(":lastModified", m_storage->lastModificationDate().toString(Qt::ISODate)); query.bindValue(":lastModified", QDate::currentDate().toString(Qt::ISODate)); query.bindValue(":baseCurrency", m_storage->pairs()["kmm-baseCurrency"]); query.bindValue(":dateRangeStart", QDate()); query.bindValue(":dateRangeEnd", QDate()); //FIXME: This modifies all m_ used in this function. // Sometimes the memory has been updated. // Should most of these be tracked in a view? // Variables actually needed are: version, fileFixVersion, creationDate, // baseCurrency, encryption, update info, and logon info. //try { //readFileInfo(); //} catch (...) { //q->startCommitUnit(Q_FUNC_INFO); //} //! @todo The following bindings are for backwards compatibility only //! remove backwards compatibility in a later version query.bindValue(":hiInstitutionId", QVariant::fromValue(q->getNextInstitutionId())); query.bindValue(":hiPayeeId", QVariant::fromValue(q->getNextPayeeId())); query.bindValue(":hiTagId", QVariant::fromValue(q->getNextTagId())); query.bindValue(":hiAccountId", QVariant::fromValue(q->getNextAccountId())); query.bindValue(":hiTransactionId", QVariant::fromValue(q->getNextTransactionId())); query.bindValue(":hiScheduleId", QVariant::fromValue(q->getNextScheduleId())); query.bindValue(":hiSecurityId", QVariant::fromValue(q->getNextSecurityId())); query.bindValue(":hiReportId", QVariant::fromValue(q->getNextReportId())); query.bindValue(":hiBudgetId", QVariant::fromValue(q->getNextBudgetId())); query.bindValue(":hiOnlineJobId", QVariant::fromValue(q->getNextOnlineJobId())); query.bindValue(":hiPayeeIdentifierId", QVariant::fromValue(q->getNextPayeeIdentifierId())); query.bindValue(":encryptData", m_encryptData); query.bindValue(":updateInProgress", "N"); query.bindValue(":logonUser", m_logonUser); query.bindValue(":logonAt", m_logonAt.toString(Qt::ISODate)); //! @todo The following bindings are for backwards compatibility only //! remove backwards compatibility in a later version query.bindValue(":institutions", (unsigned long long) m_institutions); query.bindValue(":accounts", (unsigned long long) m_accounts); query.bindValue(":payees", (unsigned long long) m_payees); query.bindValue(":tags", (unsigned long long) m_tags); query.bindValue(":transactions", (unsigned long long) m_transactions); query.bindValue(":splits", (unsigned long long) m_splits); query.bindValue(":securities", (unsigned long long) m_securities); query.bindValue(":prices", (unsigned long long) m_prices); query.bindValue(":currencies", (unsigned long long) m_currencies); query.bindValue(":schedules", (unsigned long long) m_schedules); query.bindValue(":reports", (unsigned long long) m_reports); query.bindValue(":kvps", (unsigned long long) m_kvps); query.bindValue(":budgets", (unsigned long long) m_budgets); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing FileInfo"))); // krazy:exclude=crashy } void writeReports() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT id FROM kmmReportConfig;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Report list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->reportList(); signalProgress(0, list.count(), "Writing Reports..."); query.prepare(m_db.m_tables["kmmReportConfig"].updateString()); query2.prepare(m_db.m_tables["kmmReportConfig"].insertString()); foreach (const MyMoneyReport& it, list) { if (dbList.contains(it.id())) { dbList.removeAll(it.id()); writeReport(it, query); } else { writeReport(it, query2); } signalProgress(++m_reports, 0); } if (!dbList.isEmpty()) { QVariantList idList; query.prepare("DELETE FROM kmmReportConfig WHERE id = :id"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { idList << it; } query.bindValue(":id", idList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Report")); } } void writeBudgets() { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QSqlQuery query(*q); QSqlQuery query2(*q); query.prepare("SELECT name FROM kmmBudgetConfig;"); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Budget list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toString()); QList list = m_storage->budgetList(); signalProgress(0, list.count(), "Writing Budgets..."); query.prepare(m_db.m_tables["kmmBudgetConfig"].updateString()); query2.prepare(m_db.m_tables["kmmBudgetConfig"].insertString()); foreach (const MyMoneyBudget& it, list) { if (dbList.contains(it.name())) { dbList.removeAll(it.name()); writeBudget(it, query); } else { writeBudget(it, query2); } signalProgress(++m_budgets, 0); } if (!dbList.isEmpty()) { QVariantList idList; query.prepare("DELETE FROM kmmBudgetConfig WHERE id = :id"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (const QString& it, dbList) { idList << it; } query.bindValue(":name", idList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Budget")); } } void writeOnlineJobs() { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); if (!query.exec("DELETE FROM kmmOnlineJobs;")) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QLatin1String("Clean kmmOnlineJobs table"))); const QList jobs(m_storage->onlineJobList()); signalProgress(0, jobs.count(), i18n("Inserting online jobs.")); // Create list for onlineJobs which failed and the reason therefor QList > failedJobs; int jobCount = 0; foreach (const onlineJob& job, jobs) { try { q->addOnlineJob(job); } catch (MyMoneyException& e) { // Do not save e as this may point to an inherited class failedJobs.append(QPair(job, e.what())); qDebug() << "Failed to save onlineJob" << job.id() << "Reson:" << e.what(); } signalProgress(++jobCount, 0); } if (!failedJobs.isEmpty()) { /** @todo Improve error message */ throw MYMONEYEXCEPTION(i18np("Could not save one onlineJob.", "Could not save %1 onlineJobs.", failedJobs.count())); } } /** @} */ /** * @name writeMethods * @{ * These methods bind the data fields of MyMoneyObjects to a given query and execute the query. * This is helpfull as the query has usually an update and a insert format. */ void writeInstitutionList(const QList& iList, QSqlQuery& query) { QVariantList idList; QVariantList nameList; QVariantList managerList; QVariantList routingCodeList; QVariantList addressStreetList; QVariantList addressCityList; QVariantList addressZipcodeList; QVariantList telephoneList; QList > kvpPairsList; foreach (const MyMoneyInstitution& i, iList) { idList << i.id(); nameList << i.name(); managerList << i.manager(); routingCodeList << i.sortcode(); addressStreetList << i.street(); addressCityList << i.city(); addressZipcodeList << i.postcode(); telephoneList << i.telephone(); kvpPairsList << i.pairs(); } query.bindValue(":id", idList); query.bindValue(":name", nameList); query.bindValue(":manager", managerList); query.bindValue(":routingCode", routingCodeList); query.bindValue(":addressStreet", addressStreetList); query.bindValue(":addressCity", addressCityList); query.bindValue(":addressZipcode", addressZipcodeList); query.bindValue(":telephone", telephoneList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Institution"))); writeKeyValuePairs("OFXSETTINGS", idList, kvpPairsList); // Set m_hiIdInstitutions to 0 to force recalculation the next time it is requested m_hiIdInstitutions = 0; } void writePayee(const MyMoneyPayee& p, QSqlQuery& query, bool isUserInfo = false) { if (isUserInfo) { query.bindValue(":id", "USER"); } else { query.bindValue(":id", p.id()); } query.bindValue(":name", p.name()); query.bindValue(":reference", p.reference()); query.bindValue(":email", p.email()); query.bindValue(":addressStreet", p.address()); query.bindValue(":addressCity", p.city()); query.bindValue(":addressZipcode", p.postcode()); query.bindValue(":addressState", p.state()); query.bindValue(":telephone", p.telephone()); query.bindValue(":notes", p.notes()); query.bindValue(":defaultAccountId", p.defaultAccountId()); bool ignoreCase; QString matchKeys; MyMoneyPayee::payeeMatchType type = p.matchData(ignoreCase, matchKeys); query.bindValue(":matchData", static_cast(type)); if (ignoreCase) query.bindValue(":matchIgnoreCase", "Y"); else query.bindValue(":matchIgnoreCase", "N"); query.bindValue(":matchKeys", matchKeys); if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Payee"))); // krazy:exclude=crashy if (!isUserInfo) m_hiIdPayees = 0; } void writeTag(const MyMoneyTag& ta, QSqlQuery& query) { query.bindValue(":id", ta.id()); query.bindValue(":name", ta.name()); query.bindValue(":tagColor", ta.tagColor().name()); if (ta.isClosed()) query.bindValue(":closed", "Y"); else query.bindValue(":closed", "N"); query.bindValue(":notes", ta.notes()); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Tag"))); // krazy:exclude=crashy m_hiIdTags = 0; } void writeAccountList(const QList& accList, QSqlQuery& query) { //MyMoneyMoney balance = m_storagePtr->balance(acc.id(), QDate()); QVariantList idList; QVariantList institutionIdList; QVariantList parentIdList; QVariantList lastReconciledList; QVariantList lastModifiedList; QVariantList openingDateList; QVariantList accountNumberList; QVariantList accountTypeList; QVariantList accountTypeStringList; QVariantList isStockAccountList; QVariantList accountNameList; QVariantList descriptionList; QVariantList currencyIdList; QVariantList balanceList; QVariantList balanceFormattedList; QVariantList transactionCountList; QList > pairs; QList > onlineBankingPairs; foreach (const MyMoneyAccount& a, accList) { idList << a.id(); institutionIdList << a.institutionId(); parentIdList << a.parentAccountId(); if (a.lastReconciliationDate() == QDate()) lastReconciledList << a.lastReconciliationDate(); else lastReconciledList << a.lastReconciliationDate().toString(Qt::ISODate); lastModifiedList << a.lastModified(); if (a.openingDate() == QDate()) openingDateList << a.openingDate(); else openingDateList << a.openingDate().toString(Qt::ISODate); accountNumberList << a.number(); accountTypeList << (int)a.accountType(); accountTypeStringList << MyMoneyAccount::accountTypeToString(a.accountType()); if (a.accountType() == Account::Type::Stock) isStockAccountList << "Y"; else isStockAccountList << "N"; accountNameList << a.name(); descriptionList << a.description(); currencyIdList << a.currencyId(); // This section attempts to get the balance from the database, if possible // That way, the balance fields are kept in sync. If that fails, then // It is assumed that the account actually knows its correct balance. //FIXME: Using exceptions for branching always feels like a kludge. // Look for a better way. try { - MyMoneyMoney bal = m_storagePtr->balance(a.id(), QDate()); + MyMoneyMoney bal = m_storage->balance(a.id(), QDate()); balanceList << bal.toString(); balanceFormattedList << bal.formatMoney("", -1, false); } catch (const MyMoneyException &) { balanceList << a.balance().toString(); balanceFormattedList << a.balance().formatMoney("", -1, false); } transactionCountList << quint64(m_transactionCountMap[a.id()]); //MMAccount inherits from KVPContainer AND has a KVPContainer member //so handle both pairs << a.pairs(); onlineBankingPairs << a.onlineBankingSettings().pairs(); } query.bindValue(":id", idList); query.bindValue(":institutionId", institutionIdList); query.bindValue(":parentId", parentIdList); query.bindValue(":lastReconciled", lastReconciledList); query.bindValue(":lastModified", lastModifiedList); query.bindValue(":openingDate", openingDateList); query.bindValue(":accountNumber", accountNumberList); query.bindValue(":accountType", accountTypeList); query.bindValue(":accountTypeString", accountTypeStringList); query.bindValue(":isStockAccount", isStockAccountList); query.bindValue(":accountName", accountNameList); query.bindValue(":description", descriptionList); query.bindValue(":currencyId", currencyIdList); query.bindValue(":balance", balanceList); query.bindValue(":balanceFormatted", balanceFormattedList); query.bindValue(":transactionCount", transactionCountList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Account"))); //Add in Key-Value Pairs for accounts. writeKeyValuePairs("ACCOUNT", idList, pairs); writeKeyValuePairs("ONLINEBANKING", idList, onlineBankingPairs); m_hiIdAccounts = 0; } void writeTransaction(const QString& txId, const MyMoneyTransaction& tx, QSqlQuery& query, const QString& type) { query.bindValue(":id", txId); query.bindValue(":txType", type); query.bindValue(":postDate", tx.postDate().toString(Qt::ISODate)); query.bindValue(":memo", tx.memo()); query.bindValue(":entryDate", tx.entryDate().toString(Qt::ISODate)); query.bindValue(":currencyId", tx.commodity()); query.bindValue(":bankId", tx.bankID()); if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Transaction"))); // krazy:exclude=crashy m_txPostDate = tx.postDate(); // FIXME: TEMP till Tom puts date in split object QList splitList = tx.splits(); writeSplits(txId, type, splitList); //Add in Key-Value Pairs for transactions. QVariantList idList; idList << txId; deleteKeyValuePairs("TRANSACTION", idList); QList > pairs; pairs << tx.pairs(); writeKeyValuePairs("TRANSACTION", idList, pairs); m_hiIdTransactions = 0; } void writeSplits(const QString& txId, const QString& type, const QList& splitList) { Q_Q(MyMoneyStorageSql); // first, get a list of what's on the database (see writeInstitutions) QList dbList; QList insertList; QList updateList; QList insertIdList; QList updateIdList; QSqlQuery query(*q); query.prepare("SELECT splitId FROM kmmSplits where transactionId = :id;"); query.bindValue(":id", txId); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "building Split list")); // krazy:exclude=crashy while (query.next()) dbList.append(query.value(0).toUInt()); QList::ConstIterator it; uint i = 0; QSqlQuery query2(*q); query.prepare(m_db.m_tables["kmmSplits"].updateString()); query2.prepare(m_db.m_tables["kmmSplits"].insertString()); for (it = splitList.constBegin(), i = 0; it != splitList.constEnd(); ++it, ++i) { if (dbList.contains(i)) { dbList.removeAll(i); updateList << *it; updateIdList << i; } else { ++m_splits; insertList << *it; insertIdList << i; } } if (!insertList.isEmpty()) { writeSplitList(txId, insertList, type, insertIdList, query2); writeTagSplitsList(txId, insertList, insertIdList); } if (!updateList.isEmpty()) { writeSplitList(txId, updateList, type, updateIdList, query); deleteTagSplitsList(txId, updateIdList); writeTagSplitsList(txId, updateList, updateIdList); } if (!dbList.isEmpty()) { QVector txIdList(dbList.count(), txId); QVariantList splitIdList; query.prepare("DELETE FROM kmmSplits WHERE transactionId = :txId AND splitId = :splitId"); // qCopy segfaults here, so do it with a hand-rolled loop foreach (int it, dbList) { splitIdList << it; } query.bindValue(":txId", txIdList.toList()); query.bindValue(":splitId", splitIdList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Splits")); } } void writeTagSplitsList (const QString& txId, const QList& splitList, const QList& splitIdList) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QVariantList tagIdList; QVariantList txIdList; QVariantList splitIdList_TagSplits; QVariantList tagSplitsIdList; int i = 0, l = 0; foreach (const MyMoneySplit& s, splitList) { for (l = 0; l < s.tagIdList().size(); ++l) { tagIdList << s.tagIdList()[l]; splitIdList_TagSplits << splitIdList[i]; txIdList << txId; } i++; } QSqlQuery query(*q); query.prepare(m_db.m_tables["kmmTagSplits"].insertString()); query.bindValue(":tagId", tagIdList); query.bindValue(":splitId", splitIdList_TagSplits); query.bindValue(":transactionId", txIdList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing tagSplits"))); } void writeSplitList (const QString& txId, const QList& splitList, const QString& type, const QList& splitIdList, QSqlQuery& query) { QVariantList txIdList; QVariantList typeList; QVariantList payeeIdList; QVariantList reconcileDateList; QVariantList actionList; QVariantList reconcileFlagList; QVariantList valueList; QVariantList valueFormattedList; QVariantList sharesList; QVariantList sharesFormattedList; QVariantList priceList; QVariantList priceFormattedList; QVariantList memoList; QVariantList accountIdList; QVariantList costCenterIdList; QVariantList checkNumberList; QVariantList postDateList; QVariantList bankIdList; QVariantList kvpIdList; QList > kvpPairsList; int i = 0; foreach (const MyMoneySplit& s, splitList) { txIdList << txId; typeList << type; payeeIdList << s.payeeId(); if (s.reconcileDate() == QDate()) reconcileDateList << s.reconcileDate(); else reconcileDateList << s.reconcileDate().toString(Qt::ISODate); actionList << s.action(); reconcileFlagList << (int)s.reconcileFlag(); valueList << s.value().toString(); valueFormattedList << s.value().formatMoney("", -1, false).replace(QChar(','), QChar('.')); sharesList << s.shares().toString(); - MyMoneyAccount acc = m_storagePtr->account(s.accountId()); - MyMoneySecurity sec = m_storagePtr->security(acc.currencyId()); + MyMoneyAccount acc = m_storage->account(s.accountId()); + MyMoneySecurity sec = m_storage->security(acc.currencyId()); sharesFormattedList << s.price(). formatMoney("", MyMoneyMoney::denomToPrec(sec.smallestAccountFraction()), false). replace(QChar(','), QChar('.')); MyMoneyMoney price = s.actualPrice(); if (!price.isZero()) { priceList << price.toString(); priceFormattedList << price.formatMoney ("", sec.pricePrecision(), false) .replace(QChar(','), QChar('.')); } else { priceList << QString(); priceFormattedList << QString(); } memoList << s.memo(); accountIdList << s.accountId(); costCenterIdList << s.costCenterId(); checkNumberList << s.number(); postDateList << m_txPostDate.toString(Qt::ISODate); // FIXME: when Tom puts date into split object bankIdList << s.bankID(); kvpIdList << QString(txId + QString::number(splitIdList[i])); kvpPairsList << s.pairs(); ++i; } query.bindValue(":transactionId", txIdList); query.bindValue(":txType", typeList); QVariantList iList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (int it_s, splitIdList) { iList << it_s; } query.bindValue(":splitId", iList); query.bindValue(":payeeId", payeeIdList); query.bindValue(":reconcileDate", reconcileDateList); query.bindValue(":action", actionList); query.bindValue(":reconcileFlag", reconcileFlagList); query.bindValue(":value", valueList); query.bindValue(":valueFormatted", valueFormattedList); query.bindValue(":shares", sharesList); query.bindValue(":sharesFormatted", sharesFormattedList); query.bindValue(":price", priceList); query.bindValue(":priceFormatted", priceFormattedList); query.bindValue(":memo", memoList); query.bindValue(":accountId", accountIdList); query.bindValue(":costCenterId", costCenterIdList); query.bindValue(":checkNumber", checkNumberList); query.bindValue(":postDate", postDateList); query.bindValue(":bankId", bankIdList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Split"))); deleteKeyValuePairs("SPLIT", kvpIdList); writeKeyValuePairs("SPLIT", kvpIdList, kvpPairsList); } void writeSchedule(const MyMoneySchedule& sch, QSqlQuery& query, bool insert) { query.bindValue(":id", sch.id()); query.bindValue(":name", sch.name()); query.bindValue(":type", (int)sch.type()); query.bindValue(":typeString", MyMoneySchedule::scheduleTypeToString(sch.type())); query.bindValue(":occurence", (int)sch.occurrencePeriod()); // krazy:exclude=spelling query.bindValue(":occurenceMultiplier", sch.occurrenceMultiplier()); // krazy:exclude=spelling query.bindValue(":occurenceString", sch.occurrenceToString()); // krazy:exclude=spelling query.bindValue(":paymentType", (int)sch.paymentType()); query.bindValue(":paymentTypeString", MyMoneySchedule::paymentMethodToString(sch.paymentType())); query.bindValue(":startDate", sch.startDate().toString(Qt::ISODate)); query.bindValue(":endDate", sch.endDate().toString(Qt::ISODate)); if (sch.isFixed()) { query.bindValue(":fixed", "Y"); } else { query.bindValue(":fixed", "N"); } if (sch.lastDayInMonth()) { query.bindValue(":lastDayInMonth", "Y"); } else { query.bindValue(":lastDayInMonth", "N"); } if (sch.autoEnter()) { query.bindValue(":autoEnter", "Y"); } else { query.bindValue(":autoEnter", "N"); } query.bindValue(":lastPayment", sch.lastPayment()); query.bindValue(":nextPaymentDue", sch.nextDueDate().toString(Qt::ISODate)); query.bindValue(":weekendOption", (int)sch.weekendOption()); query.bindValue(":weekendOptionString", MyMoneySchedule::weekendOptionToString(sch.weekendOption())); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Schedules"))); // krazy:exclude=crashy //store the payment history for this scheduled task. //easiest way is to delete all and re-insert; it's not a high use table query.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id;"); query.bindValue(":id", sch.id()); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("deleting Schedule Payment History"))); // krazy:exclude=crashy query.prepare(m_db.m_tables["kmmSchedulePaymentHistory"].insertString()); foreach (const QDate& it, sch.recordedPayments()) { query.bindValue(":schedId", sch.id()); query.bindValue(":payDate", it.toString(Qt::ISODate)); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Schedule Payment History"))); // krazy:exclude=crashy } //store the transaction data for this task. if (!insert) { query.prepare(m_db.m_tables["kmmTransactions"].updateString()); } else { query.prepare(m_db.m_tables["kmmTransactions"].insertString()); } writeTransaction(sch.id(), sch.transaction(), query, "S"); //FIXME: enable when schedules have KVPs. //Add in Key-Value Pairs for transactions. //deleteKeyValuePairs("SCHEDULE", sch.id()); //writeKeyValuePairs("SCHEDULE", sch.id(), sch.pairs()); } void writeSecurity(const MyMoneySecurity& security, QSqlQuery& query) { query.bindValue(":id", security.id()); query.bindValue(":name", security.name()); query.bindValue(":symbol", security.tradingSymbol()); query.bindValue(":type", static_cast(security.securityType())); query.bindValue(":typeString", MyMoneySecurity::securityTypeToString(security.securityType())); query.bindValue(":roundingMethod", static_cast(security.roundingMethod())); query.bindValue(":smallestAccountFraction", security.smallestAccountFraction()); query.bindValue(":pricePrecision", security.pricePrecision()); query.bindValue(":tradingCurrency", security.tradingCurrency()); query.bindValue(":tradingMarket", security.tradingMarket()); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Securities"))); // krazy:exclude=crashy //Add in Key-Value Pairs for security QVariantList idList; idList << security.id(); QList > pairs; pairs << security.pairs(); writeKeyValuePairs("SECURITY", idList, pairs); m_hiIdSecurities = 0; } void writePricePair(const MyMoneyPriceEntries& p) { MyMoneyPriceEntries::ConstIterator it; for (it = p.constBegin(); it != p.constEnd(); ++it) { writePrice(*it); signalProgress(++m_prices, 0); } } void writePrice(const MyMoneyPrice& p) { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); query.prepare(m_db.m_tables["kmmPrices"].insertString()); query.bindValue(":fromId", p.from()); query.bindValue(":toId", p.to()); query.bindValue(":priceDate", p.date().toString(Qt::ISODate)); query.bindValue(":price", p.rate(QString()).toString()); query.bindValue(":priceFormatted", p.rate(QString()).formatMoney("", 2)); query.bindValue(":priceSource", p.source()); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Prices"))); // krazy:exclude=crashy } void writeCurrency(const MyMoneySecurity& currency, QSqlQuery& query) { query.bindValue(":ISOcode", currency.id()); query.bindValue(":name", currency.name()); query.bindValue(":type", static_cast(currency.securityType())); query.bindValue(":typeString", MyMoneySecurity::securityTypeToString(currency.securityType())); // writing the symbol as three short ints is a PITA, but the // problem is that database drivers have incompatible ways of declaring UTF8 QString symbol = currency.tradingSymbol() + " "; const ushort* symutf = symbol.utf16(); //int ix = 0; //while (x[ix] != '\0') qDebug() << "symbol" << symbol << "char" << ix << "=" << x[ix++]; //q.bindValue(":symbol1", symbol.mid(0,1).unicode()->unicode()); //q.bindValue(":symbol2", symbol.mid(1,1).unicode()->unicode()); //q.bindValue(":symbol3", symbol.mid(2,1).unicode()->unicode()); query.bindValue(":symbol1", symutf[0]); query.bindValue(":symbol2", symutf[1]); query.bindValue(":symbol3", symutf[2]); query.bindValue(":symbolString", symbol); query.bindValue(":smallestCashFraction", currency.smallestCashFraction()); query.bindValue(":smallestAccountFraction", currency.smallestAccountFraction()); query.bindValue(":pricePrecision", currency.pricePrecision()); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Currencies"))); // krazy:exclude=crashy } void writeReport(const MyMoneyReport& rep, QSqlQuery& query) { QDomDocument d; // create a dummy XML document QDomElement e = d.createElement("REPORTS"); d.appendChild(e); rep.writeXML(d, e); // write the XML to document query.bindValue(":id", rep.id()); query.bindValue(":name", rep.name()); query.bindValue(":XML", d.toString()); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Reports"))); // krazy:exclude=crashy } void writeBudget(const MyMoneyBudget& bud, QSqlQuery& query) { QDomDocument d; // create a dummy XML document QDomElement e = d.createElement("BUDGETS"); d.appendChild(e); bud.writeXML(d, e); // write the XML to document query.bindValue(":id", bud.id()); query.bindValue(":name", bud.name()); query.bindValue(":start", bud.budgetStart()); query.bindValue(":XML", d.toString()); if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Budgets"))); // krazy:exclude=crashy } void writeKeyValuePairs(const QString& kvpType, const QVariantList& kvpId, const QList >& pairs) { Q_Q(MyMoneyStorageSql); if (pairs.empty()) return; QVariantList type; QVariantList id; QVariantList key; QVariantList value; int pairCount = 0; for (int i = 0; i < kvpId.size(); ++i) { QMap::ConstIterator it; for (it = pairs[i].constBegin(); it != pairs[i].constEnd(); ++it) { type << kvpType; id << kvpId[i]; key << it.key(); value << it.value(); } pairCount += pairs[i].size(); } QSqlQuery query(*q); query.prepare(m_db.m_tables["kmmKeyValuePairs"].insertString()); query.bindValue(":kvpType", type); query.bindValue(":kvpId", id); query.bindValue(":kvpKey", key); query.bindValue(":kvpData", value); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing KVP"))); m_kvps += pairCount; } void writeOnlineJob(const onlineJob& job, QSqlQuery& query) { Q_ASSERT(job.id().startsWith('O')); query.bindValue(":id", job.id()); query.bindValue(":type", job.taskIid()); query.bindValue(":jobSend", job.sendDate()); query.bindValue(":bankAnswerDate", job.bankAnswerDate()); switch (job.bankAnswerState()) { case onlineJob::acceptedByBank: query.bindValue(":state", QLatin1String("acceptedByBank")); break; case onlineJob::rejectedByBank: query.bindValue(":state", QLatin1String("rejectedByBank")); break; case onlineJob::abortedByUser: query.bindValue(":state", QLatin1String("abortedByUser")); break; case onlineJob::sendingError: query.bindValue(":state", QLatin1String("sendingError")); break; case onlineJob::noBankAnswer: default: query.bindValue(":state", QLatin1String("noBankAnswer")); } query.bindValue(":locked", QVariant::fromValue(job.isLocked() ? QLatin1String("Y") : QLatin1String("N"))); } void writePayeeIdentifier(const payeeIdentifier& pid, QSqlQuery& query) { query.bindValue(":id", pid.idString()); query.bindValue(":type", pid.iid()); if (!query.exec()) { // krazy:exclude=crashy qWarning() << buildError(query, Q_FUNC_INFO, QString("modifying payeeIdentifier")); throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("modifying payeeIdentifier"))); // krazy:exclude=crashy } } /** @} */ /** * @name readMethods * @{ */ void readFileInfo() { Q_Q(MyMoneyStorageSql); signalProgress(0, 1, QObject::tr("Loading file information...")); QSqlQuery query(*q); query.prepare( "SELECT " " created, lastModified, " " encryptData, logonUser, logonAt, " " (SELECT count(*) FROM kmmInstitutions) AS institutions, " " (SELECT count(*) from kmmAccounts) AS accounts, " " (SELECT count(*) FROM kmmCurrencies) AS currencies, " " (SELECT count(*) FROM kmmPayees) AS payees, " " (SELECT count(*) FROM kmmTags) AS tags, " " (SELECT count(*) FROM kmmTransactions) AS transactions, " " (SELECT count(*) FROM kmmSplits) AS splits, " " (SELECT count(*) FROM kmmSecurities) AS securities, " " (SELECT count(*) FROM kmmCurrencies) AS currencies, " " (SELECT count(*) FROM kmmSchedules) AS schedules, " " (SELECT count(*) FROM kmmPrices) AS prices, " " (SELECT count(*) FROM kmmKeyValuePairs) AS kvps, " " (SELECT count(*) FROM kmmReportConfig) AS reports, " " (SELECT count(*) FROM kmmBudgetConfig) AS budgets, " " (SELECT count(*) FROM kmmOnlineJobs) AS onlineJobs, " " (SELECT count(*) FROM kmmPayeeIdentifier) AS payeeIdentifier " "FROM kmmFileInfo;" ); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("reading FileInfo"))); // krazy:exclude=crashy if (!query.next()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("retrieving FileInfo"))); QSqlRecord rec = query.record(); m_storage->setCreationDate(GETDATE(rec.indexOf("created"))); m_storage->setLastModificationDate(GETDATE(rec.indexOf("lastModified"))); m_institutions = (ulong) GETULL(rec.indexOf("institutions")); m_accounts = (ulong) GETULL(rec.indexOf("accounts")); m_payees = (ulong) GETULL(rec.indexOf("payees")); m_tags = (ulong) GETULL(rec.indexOf("tags")); m_transactions = (ulong) GETULL(rec.indexOf("transactions")); m_splits = (ulong) GETULL(rec.indexOf("splits")); m_securities = (ulong) GETULL(rec.indexOf("securities")); m_currencies = (ulong) GETULL(rec.indexOf("currencies")); m_schedules = (ulong) GETULL(rec.indexOf("schedules")); m_prices = (ulong) GETULL(rec.indexOf("prices")); m_kvps = (ulong) GETULL(rec.indexOf("kvps")); m_reports = (ulong) GETULL(rec.indexOf("reports")); m_budgets = (ulong) GETULL(rec.indexOf("budgets")); m_onlineJobs = (ulong) GETULL(rec.indexOf("onlineJobs")); m_payeeIdentifier = (ulong) GETULL(rec.indexOf("payeeIdentifier")); m_encryptData = GETSTRING(rec.indexOf("encryptData")); m_logonUser = GETSTRING(rec.indexOf("logonUser")); m_logonAt = GETDATETIME(rec.indexOf("logonAt")); signalProgress(1, 0); m_storage->setPairs(readKeyValuePairs("STORAGE", QString("")).pairs()); } void readLogonData(); void readUserInformation(); void readInstitutions() { Q_Q(MyMoneyStorageSql); try { QMap iList = q->fetchInstitutions(); m_storage->loadInstitutions(iList); readFileInfo(); - m_storage->loadInstitutionId(m_hiIdInstitutions); } catch (const MyMoneyException &) { throw; } } void readAccounts() { Q_Q(MyMoneyStorageSql); m_storage->loadAccounts(q->fetchAccounts()); - m_storage->loadAccountId(m_hiIdAccounts); } void readTransactions(const QString& tidList, const QString& dateClause) { Q_Q(MyMoneyStorageSql); try { m_storage->loadTransactions(q->fetchTransactions(tidList, dateClause)); - m_storage->loadTransactionId(q->getNextTransactionId()); } catch (const MyMoneyException &) { throw; } } void readTransactions() { readTransactions(QString(), QString()); } void readSplit(MyMoneySplit& s, const QSqlQuery& query) const { Q_Q(const MyMoneyStorageSql); // Set these up as statics, since the field numbers should not change // during execution. static const MyMoneyDbTable& t = m_db.m_tables["kmmSplits"]; static const int splitIdCol = t.fieldNumber("splitId"); static const int transactionIdCol = t.fieldNumber("transactionId"); static const int payeeIdCol = t.fieldNumber("payeeId"); static const int reconcileDateCol = t.fieldNumber("reconcileDate"); static const int actionCol = t.fieldNumber("action"); static const int reconcileFlagCol = t.fieldNumber("reconcileFlag"); static const int valueCol = t.fieldNumber("value"); static const int sharesCol = t.fieldNumber("shares"); static const int priceCol = t.fieldNumber("price"); static const int memoCol = t.fieldNumber("memo"); static const int accountIdCol = t.fieldNumber("accountId"); static const int costCenterIdCol = t.fieldNumber("costCenterId"); static const int checkNumberCol = t.fieldNumber("checkNumber"); // static const int postDateCol = t.fieldNumber("postDate"); // FIXME - when Tom puts date into split object static const int bankIdCol = t.fieldNumber("bankId"); s.clearId(); QList tagIdList; QSqlQuery query1(*const_cast (q)); query1.prepare("SELECT tagId from kmmTagSplits where splitId = :id and transactionId = :transactionId"); query1.bindValue(":id", GETSTRING(splitIdCol)); query1.bindValue(":transactionId", GETSTRING(transactionIdCol)); if (!query1.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("reading tagId in Split"))); // krazy:exclude=crashy while (query1.next()) tagIdList << query1.value(0).toString(); s.setTagIdList(tagIdList); s.setPayeeId(GETSTRING(payeeIdCol)); s.setReconcileDate(GETDATE(reconcileDateCol)); s.setAction(GETSTRING(actionCol)); s.setReconcileFlag(static_cast(GETINT(reconcileFlagCol))); s.setValue(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(valueCol)))); s.setShares(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(sharesCol)))); s.setPrice(MyMoneyMoney(MyMoneyUtils::QStringEmpty(GETSTRING(priceCol)))); s.setMemo(GETSTRING(memoCol)); s.setAccountId(GETSTRING(accountIdCol)); s.setCostCenterId(GETSTRING(costCenterIdCol)); s.setNumber(GETSTRING(checkNumberCol)); //s.setPostDate(GETDATETIME(postDateCol)); // FIXME - when Tom puts date into split object s.setBankID(GETSTRING(bankIdCol)); return; } const MyMoneyKeyValueContainer readKeyValuePairs(const QString& kvpType, const QString& kvpId) const { Q_Q(const MyMoneyStorageSql); MyMoneyKeyValueContainer list; QSqlQuery query(*const_cast (q)); query.prepare("SELECT kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type and kvpId = :id;"); query.bindValue(":type", kvpType); query.bindValue(":id", kvpId); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("reading Kvp for %1 %2").arg(kvpType) // krazy:exclude=crashy .arg(kvpId))); while (query.next()) list.setValue(query.value(0).toString(), query.value(1).toString()); return (list); } const QHash readKeyValuePairs(const QString& kvpType, const QStringList& kvpIdList) const { Q_Q(const MyMoneyStorageSql); QHash retval; QSqlQuery query(*const_cast (q)); QString idList; if (!kvpIdList.empty()) { idList = QString(" and kvpId IN ('%1')").arg(kvpIdList.join("', '")); } QString sQuery = QString("SELECT kvpId, kvpKey, kvpData from kmmKeyValuePairs where kvpType = :type %1 order by kvpId;").arg(idList); query.prepare(sQuery); query.bindValue(":type", kvpType); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("reading Kvp List for %1").arg(kvpType))); // krazy:exclude=crashy // Reserve enough space for all values. retval.reserve(kvpIdList.size()); // The loop below is designed to limit the number of calls to // QHash::operator[] in order to speed up calls to this function. This // assumes that QString::operator== is faster. /* if (q.next()) { QString oldkey = q.value(0).toString(); MyMoneyKeyValueContainer& kvpc = retval[oldkey]; kvpc.setValue(q.value(1).toString(), q.value(2).toString()); while (q.next()) { if (q.value(0).toString() != oldkey) { oldkey = q.value(0).toString(); kvpc = retval[oldkey]; } kvpc.setValue(q.value(1).toString(), q.value(2).toString()); } } */ while (query.next()) { retval[query.value(0).toString()].setValue(query.value(1).toString(), query.value(2).toString()); } return (retval); } void readSchedules() { Q_Q(MyMoneyStorageSql); try { m_storage->loadSchedules(q->fetchSchedules()); - m_storage->loadScheduleId(q->getNextScheduleId()); } catch (const MyMoneyException &) { throw; } } void readSecurities() { Q_Q(MyMoneyStorageSql); try { m_storage->loadSecurities(q->fetchSecurities()); - m_storage->loadSecurityId(q->getNextSecurityId()); } catch (const MyMoneyException &) { throw; } } void readPrices() { // try { // m_storage->addPrice(MyMoneyPrice(from, to, date, rate, source)); // } catch (const MyMoneyException &) { // throw; // } } void readCurrencies() { Q_Q(MyMoneyStorageSql); try { m_storage->loadCurrencies(q->fetchCurrencies()); } catch (const MyMoneyException &) { throw; } } void readReports() { Q_Q(MyMoneyStorageSql); try { m_storage->loadReports(q->fetchReports()); - m_storage->loadReportId(q->getNextReportId()); } catch (const MyMoneyException &) { throw; } } void readBudgets() { Q_Q(MyMoneyStorageSql); m_storage->loadBudgets(q->fetchBudgets()); } /** @} */ void deleteTransaction(const QString& id) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); QVariantList idList; idList << id; query.prepare("DELETE FROM kmmSplits WHERE transactionId = :transactionId;"); query.bindValue(":transactionId", idList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Splits")); query.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = 'SPLIT' " "AND kvpId LIKE '?%'"); query.bindValue(1, idList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Splits KVP")); m_splits -= query.numRowsAffected(); deleteKeyValuePairs("TRANSACTION", idList); query.prepare(m_db.m_tables["kmmTransactions"].deleteString()); query.bindValue(":id", idList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Transaction")); } void deleteTagSplitsList(const QString& txId, const QList& splitIdList) { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QVariantList iList; QVariantList transactionIdList; // qCopy segfaults here, so do it with a hand-rolled loop foreach (int it_s, splitIdList) { iList << it_s; transactionIdList << txId; } QSqlQuery query(*q); query.prepare("DELETE FROM kmmTagSplits WHERE transactionId = :transactionId AND splitId = :splitId"); query.bindValue(":splitId", iList); query.bindValue(":transactionId", transactionIdList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("deleting tagSplits"))); } void deleteSchedule(const QString& id) { Q_Q(MyMoneyStorageSql); deleteTransaction(id); QSqlQuery query(*q); query.prepare("DELETE FROM kmmSchedulePaymentHistory WHERE schedId = :id"); query.bindValue(":id", id); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Schedule Payment History")); // krazy:exclude=crashy query.prepare(m_db.m_tables["kmmSchedules"].deleteString()); query.bindValue(":id", id); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "deleting Schedule")); // krazy:exclude=crashy //FIXME: enable when schedules have KVPs. //deleteKeyValuePairs("SCHEDULE", id); } void deleteKeyValuePairs(const QString& kvpType, const QVariantList& idList) { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); query.prepare("DELETE FROM kmmKeyValuePairs WHERE kvpType = :kvpType AND kvpId = :kvpId;"); QVariantList typeList; for (int i = 0; i < idList.size(); ++i) { typeList << kvpType; } query.bindValue(":kvpType", typeList); query.bindValue(":kvpId", idList); if (!query.execBatch()) { QString idString; for (int i = 0; i < idList.size(); ++i) { idString.append(idList[i].toString() + ' '); } throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("deleting kvp for %1 %2").arg(kvpType).arg(idString))); } m_kvps -= query.numRowsAffected(); } ulong calcHighId(ulong i, const QString& id) { QString nid = id; ulong high = (ulong) nid.remove(QRegExp("[A-Z]*")).toULongLong(); return std::max(high, i); } void setVersion(const QString& version); int splitState(const TransactionFilter::State& state) const { auto rc = (int)Split::State::NotReconciled; switch (state) { default: case TransactionFilter::State::NotReconciled: break; case TransactionFilter::State::Cleared: rc = (int)Split::State::Cleared; break; case TransactionFilter::State::Reconciled: rc = (int)Split::State::Reconciled; break; case TransactionFilter::State::Frozen: rc = (int)Split::State::Frozen; break; } return rc; } QDate getDate(const QString& date) const { return (date.isNull() ? QDate() : QDate::fromString(date, Qt::ISODate)); } QDateTime getDateTime(const QString& date) const { return (date.isNull() ? QDateTime() : QDateTime::fromString(date, Qt::ISODate)); } bool fileExists(const QString& dbName) { QFile f(dbName); if (!f.exists()) { m_error = i18n("SQLite file %1 does not exist", dbName); return (false); } return (true); } /** @brief a function to build a comprehensive error message for an SQL error */ QString& buildError(const QSqlQuery& query, const QString& function, const QString& messageb) const { Q_Q(const MyMoneyStorageSql); return (buildError(query, function, messageb, q)); } QString& buildError(const QSqlQuery& query, const QString& function, const QString& message, const QSqlDatabase* db) const { Q_Q(const MyMoneyStorageSql); QString s = QString("Error in function %1 : %2").arg(function).arg(message); s += QString("\nDriver = %1, Host = %2, User = %3, Database = %4") .arg(db->driverName()).arg(db->hostName()).arg(db->userName()).arg(db->databaseName()); QSqlError e = db->lastError(); s += QString("\nDriver Error: %1").arg(e.driverText()); s += QString("\nDatabase Error No %1: %2").arg(e.number()).arg(e.databaseText()); s += QString("\nText: %1").arg(e.text()); s += QString("\nError type %1").arg(e.type()); e = query.lastError(); s += QString("\nExecuted: %1").arg(query.executedQuery()); s += QString("\nQuery error No %1: %2").arg(e.number()).arg(e.text()); s += QString("\nError type %1").arg(e.type()); const_cast (q)->d_func()->m_error = s; qDebug("%s", qPrintable(s)); const_cast (q)->cancelCommitUnit(function); return (const_cast (q)->d_func()->m_error); } /** * MyMoneyStorageSql create database * * @param url pseudo-URL of database to be opened * * @return true - creation successful * @return false - could not create * */ bool createDatabase(const QUrl &url) { Q_Q(MyMoneyStorageSql); int rc = true; if (!m_driver->requiresCreation()) return(true); // not needed for sqlite QString dbName = url.path().right(url.path().length() - 1); // remove separator slash if (!m_driver->canAutocreate()) { m_error = i18n("Automatic database creation for type %1 is not currently implemented.\n" "Please create database %2 manually", q->driverName(), dbName); return (false); } // create the database (only works for mysql and postgre at present) { // for this code block, see QSqlDatabase API re removeDatabase QSqlDatabase maindb = QSqlDatabase::addDatabase(q->driverName(), "main"); maindb.setDatabaseName(m_driver->defaultDbName()); maindb.setHostName(url.host()); maindb.setUserName(url.userName()); maindb.setPassword(url.password()); if (!maindb.open()) { throw MYMONEYEXCEPTION(QString("opening database %1 in function %2") .arg(maindb.databaseName()).arg(Q_FUNC_INFO)); } else { QSqlQuery qm(maindb); QString qs = m_driver->createDbString(dbName) + ';'; if (!qm.exec(qs)) { // krazy:exclude=crashy buildError(qm, Q_FUNC_INFO, i18n("Error in create database %1; do you have create permissions?", dbName), &maindb); rc = false; } maindb.close(); } } QSqlDatabase::removeDatabase("main"); return (rc); } int upgradeDb() { Q_Q(MyMoneyStorageSql); //signalProgress(0, 1, QObject::tr("Upgrading database...")); QSqlQuery query(*q); query.prepare("SELECT version FROM kmmFileInfo;"); if (!query.exec() || !query.next()) { // krazy:exclude=crashy if (!m_newDatabase) { buildError(query, Q_FUNC_INFO, "Error retrieving file info (version)"); return(1); } else { m_dbVersion = m_db.currentVersion(); m_storage->setFileFixVersion(m_storage->currentFixVersion()); QSqlQuery query2(*q); query2.prepare("UPDATE kmmFileInfo SET version = :version, \ fixLevel = :fixLevel;"); query2.bindValue(":version", m_dbVersion); query2.bindValue(":fixLevel", m_storage->currentFixVersion()); if (!query2.exec()) { // krazy:exclude=crashy buildError(query2, Q_FUNC_INFO, "Error updating file info(version)"); return(1); } return (0); } } // prior to dbv6, 'version' format was 'dbversion.fixLevel+1' // as of dbv6, these are separate fields QString version = query.value(0).toString(); if (version.contains('.')) { m_dbVersion = query.value(0).toString().section('.', 0, 0).toUInt(); m_storage->setFileFixVersion(query.value(0).toString().section('.', 1, 1).toUInt() - 1); } else { m_dbVersion = version.toUInt(); query.prepare("SELECT fixLevel FROM kmmFileInfo;"); if (!query.exec() || !query.next()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error retrieving file info (fixLevel)"); return(1); } m_storage->setFileFixVersion(query.value(0).toUInt()); } if (m_dbVersion == m_db.currentVersion()) return 0; int rc = 0; // Drop VIEWs QStringList lowerTables = tables(QSql::AllTables); for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { (*i) = (*i).toLower(); } for (QMap::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { if (lowerTables.contains(tt.key().toLower())) { if (!query.exec("DROP VIEW " + tt.value().name() + ';')) // krazy:exclude=crashy throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("dropping view %1").arg(tt.key()))); } } while ((m_dbVersion < m_db.currentVersion()) && (rc == 0)) { switch (m_dbVersion) { case 0: if ((rc = upgradeToV1()) != 0) return (1); ++m_dbVersion; break; case 1: if ((rc = upgradeToV2()) != 0) return (1); ++m_dbVersion; break; case 2: if ((rc = upgradeToV3()) != 0) return (1); ++m_dbVersion; break; case 3: if ((rc = upgradeToV4()) != 0) return (1); ++m_dbVersion; break; case 4: if ((rc = upgradeToV5()) != 0) return (1); ++m_dbVersion; break; case 5: if ((rc = upgradeToV6()) != 0) return (1); ++m_dbVersion; break; case 6: if ((rc = upgradeToV7()) != 0) return (1); ++m_dbVersion; break; case 7: if ((rc = upgradeToV8()) != 0) return (1); ++m_dbVersion; break; case 8: if ((rc = upgradeToV9()) != 0) return (1); ++m_dbVersion; break; case 9: if ((rc = upgradeToV10()) != 0) return (1); ++m_dbVersion; break; case 10: if ((rc = upgradeToV11()) != 0) return (1); ++m_dbVersion; break; case 11: if ((rc = upgradeToV12()) != 0) return (1); ++m_dbVersion; break; default: qWarning("Unknown version number in database - %d", m_dbVersion); } } // restore VIEWs lowerTables = tables(QSql::AllTables); for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { (*i) = (*i).toLower(); } for (QMap::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { if (!lowerTables.contains(tt.key().toLower())) { if (!query.exec(tt.value().createString())) // krazy:exclude=crashy throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("creating view %1").arg(tt.key()))); } } // write updated version to DB //setVersion(QString("%1.%2").arg(m_dbVersion).arg(m_minorVersion)) query.prepare(QString("UPDATE kmmFileInfo SET version = :version;")); query.bindValue(":version", m_dbVersion); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error updating db version"); return (1); } //signalProgress(-1,-1); return (0); } int upgradeToV1() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // change kmmSplits pkey to (transactionId, splitId) if (!query.exec("ALTER TABLE kmmSplits ADD PRIMARY KEY (transactionId, splitId);")) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error updating kmmSplits pkey"); return (1); } // change kmmSplits alter checkNumber varchar(32) if (!query.exec(m_db.m_tables["kmmSplits"].modifyColumnString(m_driver, "checkNumber", // krazy:exclude=crashy MyMoneyDbColumn("checkNumber", "varchar(32)")))) { buildError(query, Q_FUNC_INFO, "Error expanding kmmSplits.checkNumber"); return (1); } // change kmmSplits add postDate datetime if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); // initialize it to same value as transaction (do it the long way round) query.prepare("SELECT id, postDate FROM kmmTransactions WHERE txType = 'N';"); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error priming kmmSplits.postDate"); return (1); } QMap tids; while (query.next()) tids[query.value(0).toString()] = query.value(1).toDateTime(); QMap::ConstIterator it; for (it = tids.constBegin(); it != tids.constEnd(); ++it) { query.prepare("UPDATE kmmSplits SET postDate=:postDate WHERE transactionId = :id;"); query.bindValue(":postDate", it.value().toString(Qt::ISODate)); query.bindValue(":id", it.key()); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "priming kmmSplits.postDate"); return(1); } } // add index to kmmKeyValuePairs to (kvpType,kvpId) QStringList list; list << "kvpType" << "kvpId"; if (!query.exec(MyMoneyDbIndex("kmmKeyValuePairs", "kmmKVPtype_id", list, false).generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "Error adding kmmKeyValuePairs index"); return (1); } // add index to kmmSplits to (accountId, txType) list.clear(); list << "accountId" << "txType"; if (!query.exec(MyMoneyDbIndex("kmmSplits", "kmmSplitsaccount_type", list, false).generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "Error adding kmmSplits index"); return (1); } // change kmmSchedulePaymentHistory pkey to (schedId, payDate) if (!query.exec("ALTER TABLE kmmSchedulePaymentHistory ADD PRIMARY KEY (schedId, payDate);")) { buildError(query, Q_FUNC_INFO, "Error updating kmmSchedulePaymentHistory pkey"); return (1); } // change kmmPrices pkey to (fromId, toId, priceDate) if (!query.exec("ALTER TABLE kmmPrices ADD PRIMARY KEY (fromId, toId, priceDate);")) { buildError(query, Q_FUNC_INFO, "Error updating kmmPrices pkey"); return (1); } // change kmmReportConfig pkey to (name) // There wasn't one previously, so no need to drop it. if (!query.exec("ALTER TABLE kmmReportConfig ADD PRIMARY KEY (name);")) { buildError(query, Q_FUNC_INFO, "Error updating kmmReportConfig pkey"); return (1); } // change kmmFileInfo add budgets, hiBudgetId unsigned bigint // change kmmFileInfo add logonUser // change kmmFileInfo add logonAt datetime if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); // change kmmAccounts add transactionCount unsigned bigint as last field if (!alterTable(m_db.m_tables["kmmAccounts"], m_dbVersion)) return (1); // calculate the transaction counts. the application logic defines an account's tx count // in such a way as to count multiple splits in a tx which reference the same account as one. // this is the only way I can think of to do this which will work in sqlite too. // inefficient, but it only gets done once... // get a list of all accounts so we'll get a zero value for those without txs query.prepare("SELECT id FROM kmmAccounts"); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error retrieving accounts for transaction counting"); return(1); } while (query.next()) { m_transactionCountMap[query.value(0).toString()] = 0; } query.prepare("SELECT accountId, transactionId FROM kmmSplits WHERE txType = 'N' ORDER BY 1, 2"); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error retrieving splits for transaction counting"); return(1); } QString lastAcc, lastTx; while (query.next()) { QString thisAcc = query.value(0).toString(); QString thisTx = query.value(1).toString(); if ((thisAcc != lastAcc) || (thisTx != lastTx)) ++m_transactionCountMap[thisAcc]; lastAcc = thisAcc; lastTx = thisTx; } QHash::ConstIterator itm; query.prepare("UPDATE kmmAccounts SET transactionCount = :txCount WHERE id = :id;"); for (itm = m_transactionCountMap.constBegin(); itm != m_transactionCountMap.constEnd(); ++itm) { query.bindValue(":txCount", QString::number(itm.value())); query.bindValue(":id", itm.key()); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, "Error updating transaction count"); return (1); } } m_transactionCountMap.clear(); return (0); } int upgradeToV2() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // change kmmSplits add price, priceFormatted fields if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); return (0); } int upgradeToV3() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSchedules - add occurrenceMultiplier // The default value is given here to populate the column. if (!query.exec("ALTER TABLE kmmSchedules ADD COLUMN " + MyMoneyDbIntColumn("occurenceMultiplier", MyMoneyDbIntColumn::SMALL, false, false, true) .generateDDL(m_driver) + " DEFAULT 0;")) { buildError(query, Q_FUNC_INFO, "Error adding kmmSchedules.occurenceMultiplier"); return (1); } //The default is less than any useful value, so as each schedule is hit, it will update //itself to the appropriate value. return 0; } int upgradeToV4() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSplits - add index on transactionId + splitId QStringList list; list << "transactionId" << "splitId"; if (!query.exec(MyMoneyDbIndex("kmmSplits", "kmmTx_Split", list, false).generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "Error adding kmmSplits index on (transactionId, splitId)"); return (1); } return 0; } int upgradeToV5() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSplits - add bankId if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); //kmmPayees - add columns "notes" "defaultAccountId" "matchData" "matchIgnoreCase" "matchKeys"; if (!alterTable(m_db.m_tables["kmmPayees"], m_dbVersion)) return (1); // kmmReportConfig - drop primary key on name since duplicate names are allowed if (!alterTable(m_db.m_tables["kmmReportConfig"], m_dbVersion)) return (1); //} return 0; } int upgradeToV6() { Q_Q(MyMoneyStorageSql); q->startCommitUnit(Q_FUNC_INFO); QSqlQuery query(*q); // kmmFileInfo - add fixLevel if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); // upgrade Mysql to InnoDB transaction-safe engine // the following is not a good way to test for mysql - think of a better way if (!m_driver->tableOptionString().isEmpty()) { for (QMap::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) { if (!query.exec(QString("ALTER TABLE %1 ENGINE = InnoDB;").arg(tt.value().name()))) { buildError(query, Q_FUNC_INFO, "Error updating to InnoDB"); return (1); } } } // the alterTable function really doesn't work too well // with adding a new column which is also to be primary key // so add the column first if (!query.exec("ALTER TABLE kmmReportConfig ADD COLUMN " + MyMoneyDbColumn("id", "varchar(32)").generateDDL(m_driver) + ';')) { buildError(query, Q_FUNC_INFO, "adding id to report table"); return(1); } QMap reportList = q->fetchReports(); // the V5 database allowed lots of duplicate reports with no // way to distinguish between them. The fetchReports call // will have effectively removed all duplicates // so we now delete from the db and re-write them if (!query.exec("DELETE FROM kmmReportConfig;")) { buildError(query, Q_FUNC_INFO, "Error deleting reports"); return (1); } // add unique id to reports table if (!alterTable(m_db.m_tables["kmmReportConfig"], m_dbVersion)) return(1); QMap::const_iterator it_r; for (it_r = reportList.constBegin(); it_r != reportList.constEnd(); ++it_r) { MyMoneyReport r = *it_r; query.prepare(m_db.m_tables["kmmReportConfig"].insertString()); writeReport(*it_r, query); } - m_storage->loadReportId(m_hiIdReports); q->endCommitUnit(Q_FUNC_INFO); return 0; } int upgradeToV7() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // add tags support // kmmFileInfo - add tags and hiTagId if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); m_tags = 0; return 0; } int upgradeToV8() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); // Added onlineJobs and payeeIdentifier if (!alterTable(m_db.m_tables["kmmFileInfo"], m_dbVersion)) return (1); return 0; } int upgradeToV9() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // kmmSplits - add bankId if (!alterTable(m_db.m_tables["kmmSplits"], m_dbVersion)) return (1); return 0; } int upgradeToV10() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); if (!alterTable(m_db.m_tables["kmmPayeesPayeeIdentifier"], m_dbVersion)) return (1); if (!alterTable(m_db.m_tables["kmmAccountsPayeeIdentifier"], m_dbVersion)) return (1); return 0; } int upgradeToV11() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); QSqlQuery query(*q); // add column roundingMethodCol to kmmSecurities if (!alterTable(m_db.m_tables["kmmSecurities"], m_dbVersion)) return 1; // add column pricePrecision to kmmCurrencies if (!alterTable(m_db.m_tables["kmmCurrencies"], m_dbVersion)) return 1; return 0; } int upgradeToV12() { Q_Q(MyMoneyStorageSql); MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); switch(haveColumnInTable(QLatin1String("kmmSchedules"), QLatin1String("lastDayInMonth"))) { case -1: return 1; case 1: // column exists, nothing to do break; case 0: // need update of kmmSchedules // add column lastDayInMonth. Simply redo the update for 10 .. 11 if (!alterTable(m_db.m_tables["kmmSchedules"], m_dbVersion-1)) return 1; break; } switch(haveColumnInTable(QLatin1String("kmmSecurities"), QLatin1String("roundingMethod"))) { case -1: return 1; case 1: // column exists, nothing to do break; case 0: // need update of kmmSecurities and kmmCurrencies // add column roundingMethodCol to kmmSecurities. Simply redo the update for 10 .. 11 if (!alterTable(m_db.m_tables["kmmSecurities"], m_dbVersion-1)) return 1; // add column pricePrecision to kmmCurrencies. Simply redo the update for 10 .. 11 if (!alterTable(m_db.m_tables["kmmCurrencies"], m_dbVersion-1)) return 1; break; } return 0; } int createTables() { Q_Q(MyMoneyStorageSql); // check tables, create if required // convert everything to lower case, since SQL standard is case insensitive // table and column names (when not delimited), but some DBMSs disagree. QStringList lowerTables = tables(QSql::AllTables); for (QStringList::iterator i = lowerTables.begin(); i != lowerTables.end(); ++i) { (*i) = (*i).toLower(); } for (QMap::ConstIterator tt = m_db.tableBegin(); tt != m_db.tableEnd(); ++tt) { if (!lowerTables.contains(tt.key().toLower())) { createTable(tt.value()); } } QSqlQuery query(*q); for (QMap::ConstIterator tt = m_db.viewBegin(); tt != m_db.viewEnd(); ++tt) { if (!lowerTables.contains(tt.key().toLower())) { if (!query.exec(tt.value().createString())) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("creating view %1").arg(tt.key()))); } } // The columns to store version info changed with version 6. Prior versions are not supported here but an error is prevented and // an old behaviour is used: call upgradeDb(). m_dbVersion = m_db.currentVersion(); if (m_dbVersion >= 6) { query.prepare(QLatin1String("INSERT INTO kmmFileInfo (version, fixLevel) VALUES(?,?);")); query.bindValue(0, m_dbVersion); query.bindValue(1, m_storage->fileFixVersion()); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("Saving database version"))); } return upgradeDb(); } void createTable(const MyMoneyDbTable& t, int version = std::numeric_limits::max()) { Q_Q(MyMoneyStorageSql); // create the tables QStringList ql = t.generateCreateSQL(m_driver, version).split('\n', QString::SkipEmptyParts); QSqlQuery query(*q); foreach (const QString& i, ql) { if (!query.exec(i)) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("creating table/index %1").arg(t.name()))); } } bool alterTable(const MyMoneyDbTable& t, int fromVersion) { Q_Q(MyMoneyStorageSql); const int toVersion = fromVersion + 1; QString tempTableName = t.name(); tempTableName.replace("kmm", "kmmtmp"); QSqlQuery query(*q); // drop primary key if it has one (and driver supports it) if (t.hasPrimaryKey(fromVersion)) { QString dropString = m_driver->dropPrimaryKeyString(t.name()); if (!dropString.isEmpty()) { if (!query.exec(dropString)) { buildError(query, Q_FUNC_INFO, QString("Error dropping old primary key from %1").arg(t.name())); return false; } } } for (MyMoneyDbTable::index_iterator i = t.indexBegin(); i != t.indexEnd(); ++i) { QString indexName = t.name() + '_' + i->name() + "_idx"; if (!query.exec(m_driver->dropIndexString(t.name(), indexName))) { buildError(query, Q_FUNC_INFO, QString("Error dropping index from %1").arg(t.name())); return false; } } if (!query.exec(QString("ALTER TABLE " + t.name() + " RENAME TO " + tempTableName + ';'))) { buildError(query, Q_FUNC_INFO, QString("Error renaming table %1").arg(t.name())); return false; } createTable(t, toVersion); if (q->getRecCount(tempTableName) > 0) { query.prepare(QString("INSERT INTO " + t.name() + " (" + t.columnList(fromVersion) + ") SELECT " + t.columnList(fromVersion) + " FROM " + tempTableName + ';')); if (!query.exec()) { // krazy:exclude=crashy buildError(query, Q_FUNC_INFO, QString("Error inserting into new table %1").arg(t.name())); return false; } } if (!query.exec(QString("DROP TABLE " + tempTableName + ';'))) { buildError(query, Q_FUNC_INFO, QString("Error dropping old table %1").arg(t.name())); return false; } return true; } void clean() { Q_Q(MyMoneyStorageSql); // delete all existing records QMap::ConstIterator it = m_db.tableBegin(); QSqlQuery query(*q); while (it != m_db.tableEnd()) { query.prepare(QString("DELETE from %1;").arg(it.key())); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("cleaning database"))); // krazy:exclude=crashy ++it; } } int isEmpty() { Q_Q(MyMoneyStorageSql); // check all tables are empty QMap::ConstIterator tt = m_db.tableBegin(); int recordCount = 0; QSqlQuery query(*q); while ((tt != m_db.tableEnd()) && (recordCount == 0)) { query.prepare(QString("select count(*) from %1;").arg((*tt).name())); if (!query.exec()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "getting record count")); // krazy:exclude=crashy if (!query.next()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, "retrieving record count")); recordCount += query.value(0).toInt(); ++tt; } if (recordCount != 0) { return (-1); // not empty } else { return (0); } } // for bug 252841 QStringList tables(QSql::TableType tt) { Q_Q(MyMoneyStorageSql); return (m_driver->tables(tt, static_cast(*q))); } //! Returns 1 in case the @a column exists in @a table, 0 if not. In case of error, -1 is returned. int haveColumnInTable(const QString& table, const QString& column) { Q_Q(MyMoneyStorageSql); QSqlQuery query(*q); QString cmd = QString("SELECT * FROM %1 LIMIT 1").arg(table); if(!query.exec(cmd)) { buildError(query, Q_FUNC_INFO, QString("Error detecting if %1 exists in %2").arg(column).arg(table)); return -1; } QSqlRecord rec = query.record(); return (rec.indexOf(column) != -1) ? 1 : 0; } /** * @brief Ensure the storagePlugin with iid was setup * * @throws MyMoneyException in case of an error which makes the use * of the plugin unavailable. */ bool setupStoragePlugin(QString iid) { Q_Q(MyMoneyStorageSql); // setupDatabase has to be called every time because this simple technique to check if was updated already // does not work if a user opens another file // also the setup is removed if the current database transaction is rolled back if (iid.isEmpty() /*|| m_loadedStoragePlugins.contains(iid)*/) return false; QString errorMsg; // TODO: port KF5 (needed for payeeidentifier plugin) #if 0 KMyMoneyPlugin::storagePlugin* plugin = KServiceTypeTrader::createInstanceFromQuery( QLatin1String("KMyMoney/sqlStoragePlugin"), QString("'%1' ~in [X-KMyMoney-PluginIid]").arg(iid.replace(QLatin1Char('\''), QLatin1String("\\'"))), 0, QVariantList(), &errorMsg ); #else KMyMoneyPlugin::storagePlugin* plugin = 0; #endif if (plugin == 0) throw MYMONEYEXCEPTION(QString("Could not load sqlStoragePlugin '%1', (error: %2)").arg(iid, errorMsg)); MyMoneyDbTransaction t(*q, Q_FUNC_INFO); if (plugin->setupDatabase(*q)) { m_loadedStoragePlugins.insert(iid); return true; } throw MYMONEYEXCEPTION(QString("Could not install sqlStoragePlugin '%1' in database.").arg(iid)); } void insertStorableObject(const databaseStoreableObject& obj, const QString& id) { Q_Q(MyMoneyStorageSql); setupStoragePlugin(obj.storagePluginIid()); if (!obj.sqlSave(*q, id)) throw MYMONEYEXCEPTION(QString("Could not save object with id '%1' in database (plugin failed).").arg(id)); } void updateStorableObject(const databaseStoreableObject& obj, const QString& id) { Q_Q(MyMoneyStorageSql); setupStoragePlugin(obj.storagePluginIid()); if (!obj.sqlModify(*q, id)) throw MYMONEYEXCEPTION(QString("Could not modify object with id '%1' in database (plugin failed).").arg(id)); } void deleteStorableObject(const databaseStoreableObject& obj, const QString& id) { Q_Q(MyMoneyStorageSql); setupStoragePlugin(obj.storagePluginIid()); if (!obj.sqlRemove(*q, id)) throw MYMONEYEXCEPTION(QString("Could not remove object with id '%1' from database (plugin failed).").arg(id)); } void alert(QString s) const // FIXME: remove... { qDebug() << s; } void signalProgress(int current, int total, const QString& msg) const { if (m_progressCallback != 0) (*m_progressCallback)(current, total, msg); } void signalProgress(int current, int total) const { signalProgress(current, total, QString()); } template ulong getNextId(const QString& table, const QString& id, const int prefixLength) const { Q_CHECK_PTR(cache); if (this->*cache == 0) { MyMoneyStorageSqlPrivate* nonConstThis = const_cast(this); nonConstThis->*cache = 1 + nonConstThis->highestNumberFromIdString(table, id, prefixLength); } Q_ASSERT(this->*cache > 0); // everything else is never a valid id return this->*cache; } //void startCommitUnit (const QString& callingFunction); //void endCommitUnit (const QString& callingFunction); //void cancelCommitUnit (const QString& callingFunction); MyMoneyStorageSql *q_ptr; // data QExplicitlySharedDataPointer m_driver; MyMoneyDbDef m_db; uint m_dbVersion; - IMyMoneySerialize *m_storage; - IMyMoneyStorage *m_storagePtr; + MyMoneyStorageMgr *m_storage; // input options bool m_loadAll; // preload all data bool m_override; // override open if already in use // error message QString m_error; // record counts ulong m_institutions; ulong m_accounts; ulong m_payees; ulong m_tags; ulong m_transactions; ulong m_splits; ulong m_securities; ulong m_prices; ulong m_currencies; ulong m_schedules; ulong m_reports; ulong m_kvps; ulong m_budgets; ulong m_onlineJobs; ulong m_payeeIdentifier; // Cache for next id to use // value 0 means data is not available and has to be loaded from the database ulong m_hiIdInstitutions; ulong m_hiIdPayees; ulong m_hiIdTags; ulong m_hiIdAccounts; ulong m_hiIdTransactions; ulong m_hiIdSchedules; ulong m_hiIdSecurities; ulong m_hiIdReports; ulong m_hiIdBudgets; ulong m_hiIdOnlineJobs; ulong m_hiIdPayeeIdentifier; ulong m_hiIdCostCenter; // encrypt option - usage TBD QString m_encryptData; /** * This variable is used to suppress status messages except during * initial data load and final write */ bool m_displayStatus; /** The following keeps track of commitment units (known as transactions in SQL * though it would be confusing to use that term within KMM). It is implemented * as a stack for debug purposes. Long term, probably a count would suffice */ QStack m_commitUnitStack; /** * This member variable is used to preload transactions for preferred accounts */ MyMoneyTransactionFilter m_preferred; /** * This member variable is used because reading prices from a file uses the 'add...' function rather than a * 'load...' function which other objects use. Having this variable allows us to avoid needing to check the * database to see if this really is a new or modified price */ bool m_readingPrices; /** * This member variable holds a map of transaction counts per account, indexed by * the account id. It is used * to avoid having to scan all transactions whenever a count is needed. It should * probably be moved into the MyMoneyAccount object; maybe we will do that once * the database code has been properly checked out */ QHash m_transactionCountMap; /** * These member variables hold the user name and date/time of logon */ QString m_logonUser; QDateTime m_logonAt; QDate m_txPostDate; // FIXME: remove when Tom puts date into split object bool m_newDatabase; /** * This member keeps the current precision to be used fro prices. * @sa setPrecision() */ static int m_precision; /** * This member keeps the current start date used for transaction retrieval. * @sa setStartDate() */ static QDate m_startDate; /** * */ QSet m_loadedStoragePlugins; void (*m_progressCallback)(int, int, const QString&); }; #endif diff --git a/kmymoney/mymoney/storage/mymoneystoragexml.cpp b/kmymoney/mymoney/storage/mymoneystoragexml.cpp index 4765f3840..6ca75f48d 100644 --- a/kmymoney/mymoney/storage/mymoneystoragexml.cpp +++ b/kmymoney/mymoney/storage/mymoneystoragexml.cpp @@ -1,1067 +1,1067 @@ /*************************************************************************** mymoneystoragexml.cpp - description ------------------- begin : Thu Oct 24 2002 copyright : (C) 2002 by Kevin Tambascio (C) 2004 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneystoragexml.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes -#include "imymoneyserialize.h" +#include "mymoneystoragemgr.h" #include "mymoneyexception.h" #include "mymoneykeyvaluecontainer.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneymoney.h" #include "mymoneyschedule.h" #include "mymoneypayee.h" #include "mymoneytag.h" #include "mymoneyreport.h" #include "mymoneybudget.h" #include "mymoneyinstitution.h" #include "mymoneystoragenames.h" #include "mymoneyutils.h" #include "mymoneyprice.h" #include "mymoneycostcenter.h" #include "mymoneytransaction.h" #include "onlinejob.h" #include "mymoneyenums.h" using namespace MyMoneyStorageTags; using namespace MyMoneyStorageNodes; using namespace MyMoneyStorageAttributes; unsigned int MyMoneyStorageXML::fileVersionRead = 0; unsigned int MyMoneyStorageXML::fileVersionWrite = 0; class MyMoneyStorageXML::Private { friend class MyMoneyStorageXML; public: Private() : m_nextTransactionID(0) {} QMap iList; QMap aList; QMap tList; QMap pList; QMap taList; QMap sList; QMap secList; QMap rList; QMap bList; QMap onlineJobList; QMap prList; QMap ccList; QString m_fromSecurity; QString m_toSecurity; unsigned long m_nextTransactionID; static const int TRANSACTION_ID_SIZE = 18; QString nextTransactionID() { QString id; id.setNum(++m_nextTransactionID); id = 'T' + id.rightJustified(TRANSACTION_ID_SIZE, '0'); return id; } }; class MyMoneyXmlContentHandler : public QXmlContentHandler { public: MyMoneyXmlContentHandler(MyMoneyStorageXML* reader); virtual ~MyMoneyXmlContentHandler() {} virtual void setDocumentLocator(QXmlLocator * locator) { m_loc = locator; } virtual bool startDocument(); virtual bool endDocument(); virtual bool startPrefixMapping(const QString & prefix, const QString & uri); virtual bool endPrefixMapping(const QString & prefix); virtual bool startElement(const QString & namespaceURI, const QString & localName, const QString & qName, const QXmlAttributes & atts); virtual bool endElement(const QString & namespaceURI, const QString & localName, const QString & qName); virtual bool characters(const QString & ch); virtual bool ignorableWhitespace(const QString & ch); virtual bool processingInstruction(const QString & target, const QString & data); virtual bool skippedEntity(const QString & name); virtual QString errorString() const ; private: MyMoneyStorageXML* m_reader; QXmlLocator* m_loc; int m_level; int m_elementCount; QDomDocument m_doc; /** * @node Text in the xml file is not added to this QDomElement. Only tags and their attributes are added. */ QDomElement m_baseNode; /** * @node Text in the xml file is not added to this QDomElement. Only tags and their attributes are added. */ QDomElement m_currNode; QString m_errMsg; }; MyMoneyXmlContentHandler::MyMoneyXmlContentHandler(MyMoneyStorageXML* reader) : m_reader(reader), m_loc(0), m_level(0), m_elementCount(0) { } bool MyMoneyXmlContentHandler::startDocument() { qDebug("startDocument"); return true; } bool MyMoneyXmlContentHandler::endDocument() { qDebug("endDocument"); return true; } bool MyMoneyXmlContentHandler::skippedEntity(const QString & /* name */) { // qDebug(QString("Skipped entity '%1'").arg(name)); return true; } bool MyMoneyXmlContentHandler::startPrefixMapping(const QString& /*prefix */, const QString & /* uri */) { // qDebug(QString("start prefix '%1', '%2'").arg(prefix).arg(uri)); return true; } bool MyMoneyXmlContentHandler::endPrefixMapping(const QString& /* prefix */) { // qDebug(QString("end prefix '%1'").arg(prefix)); return true; } bool MyMoneyXmlContentHandler::startElement(const QString& /* namespaceURI */, const QString& /* localName */, const QString& qName, const QXmlAttributes & atts) { if (m_level == 0) { QString s = qName.toUpper(); if (s == nodeNames[nnTransaction] || s == nodeNames[nnAccount] || s == nodeNames[nnPrice] || s == nodeNames[nnPayee] || s == nodeNames[nnTag] || s == nodeNames[nnCostCenter] || s == nodeNames[nnCurrency] || s == nodeNames[nnSecurity] || s == nodeNames[nnKeyValuePairs] || s == nodeNames[nnInstitution] || s == nodeNames[nnReport] || s == nodeNames[nnBudget] || s == tagNames[tnFileInfo] || s == tagNames[tnUser] || s == nodeNames[nnScheduleTX] || s == nodeNames[nnOnlineJob]) { m_baseNode = m_doc.createElement(qName); for (int i = 0; i < atts.count(); ++i) { m_baseNode.setAttribute(atts.qName(i), atts.value(i)); } m_currNode = m_baseNode; m_level = 1; } else if (s == tagNames[tnTransactions]) { if (atts.count()) { int count = atts.value(attrNames[anCount]).toInt(); m_reader->signalProgress(0, count, i18n("Loading transactions...")); m_elementCount = 0; } } else if (s == tagNames[tnAccounts]) { if (atts.count()) { int count = atts.value(attrNames[anCount]).toInt(); m_reader->signalProgress(0, count, i18n("Loading accounts...")); m_elementCount = 0; } } else if (s == tagNames[tnSecurities]) { qDebug("reading securities"); if (atts.count()) { int count = atts.value(attrNames[anCount]).toInt(); m_reader->signalProgress(0, count, i18n("Loading securities...")); m_elementCount = 0; } } else if (s == tagNames[tnCurrencies]) { if (atts.count()) { int count = atts.value(attrNames[anCount]).toInt(); m_reader->signalProgress(0, count, i18n("Loading currencies...")); m_elementCount = 0; } } else if (s == tagNames[tnReports]) { if (atts.count()) { int count = atts.value(attrNames[anCount]).toInt(); m_reader->signalProgress(0, count, i18n("Loading reports...")); m_elementCount = 0; } } else if (s == tagNames[tnPrices]) { if (atts.count()) { int count = atts.value(attrNames[anCount]).toInt(); m_reader->signalProgress(0, count, i18n("Loading prices...")); m_elementCount = 0; } } else if (s == nodeNames[nnPricePair]) { if (atts.count()) { m_reader->d->m_fromSecurity = atts.value(attrNames[anFrom]); m_reader->d->m_toSecurity = atts.value(attrNames[anTo]); } } else if (s == tagNames[tnCostCenters]) { if(atts.count()) { int count = atts.value(attrNames[anCount]).toInt(); m_reader->signalProgress(0, count, i18n("Loading cost center...")); m_elementCount = 0; } } } else { m_level++; QDomElement node = m_doc.createElement(qName); for (int i = 0; i < atts.count(); ++i) { node.setAttribute(atts.qName(i), atts.value(i)); } m_currNode.appendChild(node); m_currNode = node; } return true; } bool MyMoneyXmlContentHandler::endElement(const QString& /* namespaceURI */, const QString& /* localName */, const QString& qName) { bool rc = true; QString s = qName.toUpper(); if (m_level) { m_currNode = m_currNode.parentNode().toElement(); m_level--; if (!m_level) { try { if (s == nodeNames[nnTransaction]) { MyMoneyTransaction t0(m_baseNode); if (!t0.id().isEmpty()) { MyMoneyTransaction t1(m_reader->d->nextTransactionID(), t0); m_reader->d->tList[t1.uniqueSortKey()] = t1; } m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnAccount]) { MyMoneyAccount a(m_baseNode); if (!a.id().isEmpty()) m_reader->d->aList[a.id()] = a; m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnPayee]) { MyMoneyPayee p(m_baseNode); if (!p.id().isEmpty()) m_reader->d->pList[p.id()] = p; m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnTag]) { MyMoneyTag ta(m_baseNode); if (!ta.id().isEmpty()) m_reader->d->taList[ta.id()] = ta; m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnCurrency]) { MyMoneySecurity s(m_baseNode); if (!s.id().isEmpty()) m_reader->d->secList[s.id()] = s; m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnSecurity]) { MyMoneySecurity s(m_baseNode); if (!s.id().isEmpty()) m_reader->d->secList[s.id()] = s; m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnKeyValuePairs]) { MyMoneyKeyValueContainer kvp(m_baseNode); m_reader->m_storage->setPairs(kvp.pairs()); } else if (s == nodeNames[nnInstitution]) { MyMoneyInstitution i(m_baseNode); if (!i.id().isEmpty()) m_reader->d->iList[i.id()] = i; } else if (s == nodeNames[nnReport]) { MyMoneyReport r(m_baseNode); if (!r.id().isEmpty()) m_reader->d->rList[r.id()] = r; m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnBudget]) { MyMoneyBudget b(m_baseNode); if (!b.id().isEmpty()) m_reader->d->bList[b.id()] = b; } else if (s == tagNames[tnFileInfo]) { rc = m_reader->readFileInformation(m_baseNode); m_reader->signalProgress(-1, -1); } else if (s == tagNames[tnUser]) { rc = m_reader->readUserInformation(m_baseNode); m_reader->signalProgress(-1, -1); } else if (s == nodeNames[nnScheduleTX]) { MyMoneySchedule s(m_baseNode); if (!s.id().isEmpty()) m_reader->d->sList[s.id()] = s; } else if (s == nodeNames[nnPrice]) { MyMoneyPrice p(m_reader->d->m_fromSecurity, m_reader->d->m_toSecurity, m_baseNode); m_reader->d->prList[MyMoneySecurityPair(m_reader->d->m_fromSecurity, m_reader->d->m_toSecurity)][p.date()] = p; m_reader->signalProgress(++m_elementCount, 0); } else if (s == nodeNames[nnOnlineJob]) { onlineJob job(m_baseNode); if (!job.id().isEmpty()) m_reader->d->onlineJobList[job.id()] = job; } else if (s == nodeNames[nnCostCenter]) { MyMoneyCostCenter c(m_baseNode); if(!c.id().isEmpty()) { m_reader->d->ccList[c.id()] = c; } m_reader->signalProgress(++m_elementCount, 0); } else { m_errMsg = i18n("Unknown XML tag %1 found in line %2", qName, m_loc->lineNumber()); qWarning() << m_errMsg; rc = false; } } catch (const MyMoneyException &e) { m_errMsg = i18n("Exception while creating a %1 element: %2", s, e.what()); qWarning() << m_errMsg; rc = false; } m_doc = QDomDocument(); } } else { if (s == tagNames[tnInstitutions]) { // last institution read, now dump them into the engine m_reader->m_storage->loadInstitutions(m_reader->d->iList); m_reader->d->iList.clear(); } else if (s == tagNames[tnAccounts]) { // last account read, now dump them into the engine m_reader->m_storage->loadAccounts(m_reader->d->aList); m_reader->d->aList.clear(); m_reader->signalProgress(-1, -1); } else if (s == tagNames[tnPayees]) { // last payee read, now dump them into the engine m_reader->m_storage->loadPayees(m_reader->d->pList); m_reader->d->pList.clear(); } else if (s == tagNames[tnTags]) { // last tag read, now dump them into the engine m_reader->m_storage->loadTags(m_reader->d->taList); m_reader->d->taList.clear(); } else if (s == tagNames[tnTransactions]) { // last transaction read, now dump them into the engine m_reader->m_storage->loadTransactions(m_reader->d->tList); m_reader->d->tList.clear(); m_reader->signalProgress(-1, -1); } else if (s == tagNames[tnSchedules]) { // last schedule read, now dump them into the engine m_reader->m_storage->loadSchedules(m_reader->d->sList); m_reader->d->sList.clear(); } else if (s == tagNames[tnSecurities]) { // last security read, now dump them into the engine m_reader->m_storage->loadSecurities(m_reader->d->secList); m_reader->d->secList.clear(); m_reader->signalProgress(-1, -1); } else if (s == tagNames[tnCurrencies]) { // last currency read, now dump them into the engine m_reader->m_storage->loadCurrencies(m_reader->d->secList); m_reader->d->secList.clear(); m_reader->signalProgress(-1, -1); } else if (s == tagNames[tnReports]) { // last report read, now dump them into the engine m_reader->m_storage->loadReports(m_reader->d->rList); m_reader->d->rList.clear(); m_reader->signalProgress(-1, -1); } else if (s == tagNames[tnBudgets]) { // last budget read, now dump them into the engine m_reader->m_storage->loadBudgets(m_reader->d->bList); m_reader->d->bList.clear(); } else if (s == tagNames[tnPrices]) { // last price read, now dump them into the engine m_reader->m_storage->loadPrices(m_reader->d->prList); m_reader->d->bList.clear(); m_reader->signalProgress(-1, -1); } else if (s == tagNames[tnOnlineJobs]) { m_reader->m_storage->loadOnlineJobs(m_reader->d->onlineJobList); m_reader->d->onlineJobList.clear(); } else if (s == tagNames[tnCostCenters]) { m_reader->m_storage->loadCostCenters(m_reader->d->ccList); m_reader->d->ccList.clear(); m_reader->signalProgress(-1, -1); } } return rc; } bool MyMoneyXmlContentHandler::characters(const QString& /* ch */) { return true; } bool MyMoneyXmlContentHandler::ignorableWhitespace(const QString& /* ch */) { return true; } bool MyMoneyXmlContentHandler::processingInstruction(const QString& /* target */, const QString& /* data */) { return true; } QString MyMoneyXmlContentHandler::errorString() const { return m_errMsg; } MyMoneyStorageXML::MyMoneyStorageXML() : m_progressCallback(0), m_storage(0), m_doc(0), d(new Private()) { } MyMoneyStorageXML::~MyMoneyStorageXML() { delete d; } // Function to read in the file, send to XML parser. -void MyMoneyStorageXML::readFile(QIODevice* pDevice, IMyMoneySerialize* storage) +void MyMoneyStorageXML::readFile(QIODevice* pDevice, MyMoneyStorageMgr* storage) { Q_CHECK_PTR(storage); Q_CHECK_PTR(pDevice); if (!storage) return; m_storage = storage; m_doc = new QDomDocument; Q_CHECK_PTR(m_doc); qDebug("reading file"); // creating the QXmlInputSource object based on a QIODevice object // reads all data of the underlying object into memory. I have not // found an object that reads on the fly. I tried to derive one myself, // but there could be a severe problem with decoding when reading // blocks of data and not a stream. So I left it the way it is. (ipwizard) QXmlInputSource xml(pDevice); qDebug("start parsing file"); MyMoneyXmlContentHandler mmxml(this); QXmlSimpleReader reader; reader.setContentHandler(&mmxml); if (!reader.parse(&xml, false)) { delete m_doc; m_doc = 0; signalProgress(-1, -1); throw MYMONEYEXCEPTION("File was not parsable!"); } // check if we need to build up the account balances if (fileVersionRead < 2) m_storage->rebuildAccountBalances(); delete m_doc; m_doc = 0; // this seems to be nonsense, but it clears the dirty flag // as a side-effect. m_storage->setLastModificationDate(m_storage->lastModificationDate()); m_storage = 0; //hides the progress bar. signalProgress(-1, -1); } -void MyMoneyStorageXML::writeFile(QIODevice* qf, IMyMoneySerialize* storage) +void MyMoneyStorageXML::writeFile(QIODevice* qf, MyMoneyStorageMgr* storage) { Q_CHECK_PTR(qf); Q_CHECK_PTR(storage); if (!storage) { return; } m_storage = storage; // qDebug("XMLWRITER: Starting file write"); m_doc = new QDomDocument(tagNames[tnKMMFile]); Q_CHECK_PTR(m_doc); QDomProcessingInstruction instruct = m_doc->createProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""); m_doc->appendChild(instruct); QDomElement mainElement = m_doc->createElement(tagNames[tnKMMFile]); m_doc->appendChild(mainElement); QDomElement fileInfo = m_doc->createElement(tagNames[tnFileInfo]); writeFileInformation(fileInfo); mainElement.appendChild(fileInfo); QDomElement userInfo = m_doc->createElement(tagNames[tnUser]); writeUserInformation(userInfo); mainElement.appendChild(userInfo); QDomElement institutions = m_doc->createElement(tagNames[tnInstitutions]); writeInstitutions(institutions); mainElement.appendChild(institutions); QDomElement payees = m_doc->createElement(tagNames[tnPayees]); writePayees(payees); mainElement.appendChild(payees); QDomElement costCenters = m_doc->createElement(tagNames[tnCostCenters]); writeCostCenters(costCenters); mainElement.appendChild(costCenters); QDomElement tags = m_doc->createElement(tagNames[tnTags]); writeTags(tags); mainElement.appendChild(tags); QDomElement accounts = m_doc->createElement(tagNames[tnAccounts]); writeAccounts(accounts); mainElement.appendChild(accounts); QDomElement transactions = m_doc->createElement(tagNames[tnTransactions]); writeTransactions(transactions); mainElement.appendChild(transactions); QDomElement keyvalpairs = writeKeyValuePairs(m_storage->pairs()); mainElement.appendChild(keyvalpairs); QDomElement schedules = m_doc->createElement(tagNames[tnSchedules]); writeSchedules(schedules); mainElement.appendChild(schedules); QDomElement equities = m_doc->createElement(tagNames[tnSecurities]); writeSecurities(equities); mainElement.appendChild(equities); QDomElement currencies = m_doc->createElement(tagNames[tnCurrencies]); writeCurrencies(currencies); mainElement.appendChild(currencies); QDomElement prices = m_doc->createElement(tagNames[tnPrices]); writePrices(prices); mainElement.appendChild(prices); QDomElement reports = m_doc->createElement(tagNames[tnReports]); writeReports(reports); mainElement.appendChild(reports); QDomElement budgets = m_doc->createElement(tagNames[tnBudgets]); writeBudgets(budgets); mainElement.appendChild(budgets); QDomElement onlineJobs = m_doc->createElement(tagNames[tnOnlineJobs]); writeOnlineJobs(onlineJobs); mainElement.appendChild(onlineJobs); - + QTextStream stream(qf); stream.setCodec("UTF-8"); stream << m_doc->toString(); delete m_doc; m_doc = 0; //hides the progress bar. signalProgress(-1, -1); // this seems to be nonsense, but it clears the dirty flag // as a side-effect. m_storage->setLastModificationDate(m_storage->lastModificationDate()); m_storage = 0; } bool MyMoneyStorageXML::readFileInformation(const QDomElement& fileInfo) { signalProgress(0, 3, i18n("Loading file information...")); bool rc = true; QDomElement temp = findChildElement(getElName(enCreationDate), fileInfo); if (temp == QDomElement()) { rc = false; } QString strDate = MyMoneyUtils::QStringEmpty(temp.attribute(attrNames[anDate])); m_storage->setCreationDate(MyMoneyUtils::stringToDate(strDate)); signalProgress(1, 0); temp = findChildElement(getElName(enLastModifiedDate), fileInfo); if (temp == QDomElement()) { rc = false; } strDate = MyMoneyUtils::QStringEmpty(temp.attribute(attrNames[anDate])); m_storage->setLastModificationDate(MyMoneyUtils::stringToDate(strDate)); signalProgress(2, 0); temp = findChildElement(getElName(enVersion), fileInfo); if (temp == QDomElement()) { rc = false; } QString strVersion = MyMoneyUtils::QStringEmpty(temp.attribute(attrNames[anID])); fileVersionRead = strVersion.toUInt(0, 16); temp = findChildElement(getElName(enFixVersion), fileInfo); if (temp != QDomElement()) { QString strFixVersion = MyMoneyUtils::QStringEmpty(temp.attribute(attrNames[anID])); m_storage->setFileFixVersion(strFixVersion.toUInt()); // skip KMyMoneyView::fixFile_2() if (m_storage->fileFixVersion() == 2) { m_storage->setFileFixVersion(3); } } // FIXME The old version stuff used this rather odd number // We now use increments if (fileVersionRead == VERSION_0_60_XML) fileVersionRead = 1; signalProgress(3, 0); return rc; } void MyMoneyStorageXML::writeFileInformation(QDomElement& fileInfo) { QDomElement creationDate = m_doc->createElement(getElName(enCreationDate)); creationDate.setAttribute(attrNames[anDate], MyMoneyUtils::dateToString(m_storage->creationDate())); fileInfo.appendChild(creationDate); QDomElement lastModifiedDate = m_doc->createElement(getElName(enLastModifiedDate)); lastModifiedDate.setAttribute(attrNames[anDate], MyMoneyUtils::dateToString(m_storage->lastModificationDate())); fileInfo.appendChild(lastModifiedDate); QDomElement version = m_doc->createElement(getElName(enVersion)); version.setAttribute(attrNames[anID], "1"); fileInfo.appendChild(version); QDomElement fixVersion = m_doc->createElement(getElName(enFixVersion)); fixVersion.setAttribute(attrNames[anID], m_storage->fileFixVersion()); fileInfo.appendChild(fixVersion); } void MyMoneyStorageXML::writeUserInformation(QDomElement& userInfo) { MyMoneyPayee user = m_storage->user(); userInfo.setAttribute(attrNames[anName], user.name()); userInfo.setAttribute(attrNames[anEmail], user.email()); QDomElement address = m_doc->createElement(getElName(enAddress)); address.setAttribute(attrNames[anStreet], user.address()); address.setAttribute(attrNames[anCity], user.city()); address.setAttribute(attrNames[anCountry], user.state()); address.setAttribute(attrNames[anZipCode], user.postcode()); address.setAttribute(attrNames[anTelephone], user.telephone()); userInfo.appendChild(address); } bool MyMoneyStorageXML::readUserInformation(const QDomElement& userElement) { bool rc = true; signalProgress(0, 1, i18n("Loading user information...")); MyMoneyPayee user; user.setName(MyMoneyUtils::QStringEmpty(userElement.attribute(attrNames[anName]))); user.setEmail(MyMoneyUtils::QStringEmpty(userElement.attribute(attrNames[anEmail]))); QDomElement addressNode = findChildElement(getElName(enAddress), userElement); if (!addressNode.isNull()) { user.setAddress(MyMoneyUtils::QStringEmpty(addressNode.attribute(attrNames[anStreet]))); user.setCity(MyMoneyUtils::QStringEmpty(addressNode.attribute(attrNames[anCity]))); user.setState(MyMoneyUtils::QStringEmpty(addressNode.attribute(attrNames[anCountry]))); user.setPostcode(MyMoneyUtils::QStringEmpty(addressNode.attribute(attrNames[anZipCode]))); user.setTelephone(MyMoneyUtils::QStringEmpty(addressNode.attribute(attrNames[anTelephone]))); } m_storage->setUser(user); signalProgress(1, 0); return rc; } void MyMoneyStorageXML::writeInstitutions(QDomElement& institutions) { const QList list = m_storage->institutionList(); QList::ConstIterator it; institutions.setAttribute(attrNames[anCount], list.count()); for (it = list.begin(); it != list.end(); ++it) writeInstitution(institutions, *it); } void MyMoneyStorageXML::writeInstitution(QDomElement& institution, const MyMoneyInstitution& i) { i.writeXML(*m_doc, institution); } void MyMoneyStorageXML::writePayees(QDomElement& payees) { const QList list = m_storage->payeeList(); QList::ConstIterator it; payees.setAttribute(attrNames[anCount], list.count()); for (it = list.begin(); it != list.end(); ++it) writePayee(payees, *it); } void MyMoneyStorageXML::writePayee(QDomElement& payee, const MyMoneyPayee& p) { p.writeXML(*m_doc, payee); } void MyMoneyStorageXML::writeTags(QDomElement& tags) { const QList list = m_storage->tagList(); QList::ConstIterator it; tags.setAttribute(attrNames[anCount], list.count()); for (it = list.begin(); it != list.end(); ++it) writeTag(tags, *it); } void MyMoneyStorageXML::writeTag(QDomElement& tag, const MyMoneyTag& ta) { ta.writeXML(*m_doc, tag); } void MyMoneyStorageXML::writeAccounts(QDomElement& accounts) { QList list; m_storage->accountList(list); QList::ConstIterator it; accounts.setAttribute(attrNames[anCount], list.count() + 5); writeAccount(accounts, m_storage->asset()); writeAccount(accounts, m_storage->liability()); writeAccount(accounts, m_storage->expense()); writeAccount(accounts, m_storage->income()); writeAccount(accounts, m_storage->equity()); signalProgress(0, list.count(), i18n("Saving accounts...")); int i = 0; for (it = list.constBegin(); it != list.constEnd(); ++it) { writeAccount(accounts, *it); signalProgress(++i, 0); } } void MyMoneyStorageXML::writeAccount(QDomElement& account, const MyMoneyAccount& p) { p.writeXML(*m_doc, account); } void MyMoneyStorageXML::writeTransactions(QDomElement& transactions) { MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList list; m_storage->transactionList(list, filter); transactions.setAttribute(attrNames[anCount], list.count()); QList::ConstIterator it; signalProgress(0, list.count(), i18n("Saving transactions...")); int i = 0; for (it = list.constBegin(); it != list.constEnd(); ++it) { writeTransaction(transactions, *it); signalProgress(++i, 0); } } void MyMoneyStorageXML::writeTransaction(QDomElement& transaction, const MyMoneyTransaction& tx) { tx.writeXML(*m_doc, transaction); } void MyMoneyStorageXML::writeSchedules(QDomElement& scheduled) { const QList list = m_storage->scheduleList(QString(), eMyMoney::Schedule::Type::Any, eMyMoney::Schedule::Occurrence::Any, eMyMoney::Schedule::PaymentType::Any, QDate(), QDate(), false); QList::ConstIterator it; scheduled.setAttribute(attrNames[anCount], list.count()); for (it = list.constBegin(); it != list.constEnd(); ++it) { this->writeSchedule(scheduled, *it); } } void MyMoneyStorageXML::writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx) { tx.writeXML(*m_doc, scheduledTx); } void MyMoneyStorageXML::writeSecurities(QDomElement& equities) { const QList securityList = m_storage->securityList(); equities.setAttribute(attrNames[anCount], securityList.count()); if (securityList.size()) { for (QList::ConstIterator it = securityList.constBegin(); it != securityList.constEnd(); ++it) { writeSecurity(equities, (*it)); } } } void MyMoneyStorageXML::writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security) { security.writeXML(*m_doc, securityElement); } void MyMoneyStorageXML::writeCurrencies(QDomElement& currencies) { const QList currencyList = m_storage->currencyList(); currencies.setAttribute(attrNames[anCount], currencyList.count()); if (currencyList.size()) { for (QList::ConstIterator it = currencyList.constBegin(); it != currencyList.constEnd(); ++it) { writeSecurity(currencies, (*it)); } } } void MyMoneyStorageXML::writeReports(QDomElement& parent) { const QList list = m_storage->reportList(); QList::ConstIterator it; parent.setAttribute(attrNames[anCount], list.count()); signalProgress(0, list.count(), i18n("Saving reports...")); unsigned i = 0; for (it = list.constBegin(); it != list.constEnd(); ++it) { writeReport(parent, (*it)); signalProgress(++i, 0); } } void MyMoneyStorageXML::writeReport(QDomElement& report, const MyMoneyReport& r) { r.writeXML(*m_doc, report); } void MyMoneyStorageXML::writeBudgets(QDomElement& parent) { const QList list = m_storage->budgetList(); QList::ConstIterator it; parent.setAttribute(attrNames[anCount], list.count()); signalProgress(0, list.count(), i18n("Saving budgets...")); unsigned i = 0; for (it = list.constBegin(); it != list.constEnd(); ++it) { writeBudget(parent, (*it)); signalProgress(++i, 0); } } void MyMoneyStorageXML::writeBudget(QDomElement& budget, const MyMoneyBudget& b) { b.writeXML(*m_doc, budget); } void MyMoneyStorageXML::writeOnlineJobs(QDomElement& parent) { const QList list = m_storage->onlineJobList(); parent.setAttribute(attrNames[anCount], list.count()); signalProgress(0, list.count(), i18n("Saving online banking orders...")); unsigned i = 0; QList::ConstIterator end = list.constEnd(); for (QList::ConstIterator it = list.constBegin(); it != end; ++it) { writeOnlineJob(parent, *it); signalProgress(++i, 0); } } void MyMoneyStorageXML::writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job) { job.writeXML(*m_doc, onlineJobs); } void MyMoneyStorageXML::writeCostCenters(QDomElement& parent) { const QList list = m_storage->costCenterList(); parent.setAttribute(attrNames[anCount], list.count()); signalProgress(0, list.count(), i18n("Saving costcenters...")); unsigned i = 0; Q_FOREACH(MyMoneyCostCenter costCenter, list) { writeCostCenter(parent, costCenter); signalProgress(++i, 0); } } void MyMoneyStorageXML::writeCostCenter(QDomElement& costCenters, const MyMoneyCostCenter& costCenter) { costCenter.writeXML(*m_doc, costCenters); } QDomElement MyMoneyStorageXML::findChildElement(const QString& name, const QDomElement& root) { QDomNode child = root.firstChild(); while (!child.isNull()) { if (child.isElement()) { QDomElement childElement = child.toElement(); if (name == childElement.tagName()) { return childElement; } } child = child.nextSibling(); } return QDomElement(); } QDomElement MyMoneyStorageXML::writeKeyValuePairs(const QMap pairs) { if (m_doc) { QDomElement keyValPairs = m_doc->createElement(nodeNames[nnKeyValuePairs]); QMap::const_iterator it; for (it = pairs.constBegin(); it != pairs.constEnd(); ++it) { QDomElement pair = m_doc->createElement(getElName(enPair)); pair.setAttribute(attrNames[anKey], it.key()); pair.setAttribute(attrNames[anValue], it.value()); keyValPairs.appendChild(pair); } return keyValPairs; } return QDomElement(); } void MyMoneyStorageXML::writePrices(QDomElement& prices) { const MyMoneyPriceList list = m_storage->priceList(); MyMoneyPriceList::ConstIterator it; prices.setAttribute(attrNames[anCount], list.count()); for (it = list.constBegin(); it != list.constEnd(); ++it) { QDomElement price = m_doc->createElement(nodeNames[nnPricePair]); price.setAttribute(attrNames[anFrom], it.key().first); price.setAttribute(attrNames[anTo], it.key().second); writePricePair(price, *it); prices.appendChild(price); } } void MyMoneyStorageXML::writePricePair(QDomElement& price, const MyMoneyPriceEntries& p) { MyMoneyPriceEntries::ConstIterator it; for (it = p.constBegin(); it != p.constEnd(); ++it) { QDomElement entry = m_doc->createElement(nodeNames[nnPrice]); writePrice(entry, *it); price.appendChild(entry); } } void MyMoneyStorageXML::writePrice(QDomElement& price, const MyMoneyPrice& p) { price.setAttribute(attrNames[anDate], p.date().toString(Qt::ISODate)); price.setAttribute(attrNames[anPrice], p.rate(QString()).toString()); price.setAttribute(attrNames[anSource], p.source()); } void MyMoneyStorageXML::setProgressCallback(void(*callback)(int, int, const QString&)) { m_progressCallback = callback; } void MyMoneyStorageXML::signalProgress(int current, int total, const QString& msg) { if (m_progressCallback != 0) (*m_progressCallback)(current, total, msg); } #if 0 /*! This convenience function returns all of the remaining data in the device. @note It's copied from the original Qt sources and modified to fix a problem with KFilterDev that does not correctly return atEnd() status in certain circumstances which caused our application to lock at startup. */ QByteArray QIODevice::readAll() { if (isDirectAccess()) { // we know the size int n = size() - at(); // ### fix for 64-bit or large files? int totalRead = 0; QByteArray ba(n); char* c = ba.data(); while (n) { int r = read(c, n); if (r < 0) return QByteArray(); n -= r; c += r; totalRead += r; // If we have a translated file, then it is possible that // we read less bytes than size() reports if (atEnd()) { ba.resize(totalRead); break; } } return ba; } else { // read until we reach the end const int blocksize = 512; int nread = 0; QByteArray ba; int r = 1; while (!atEnd() && r != 0) { ba.resize(nread + blocksize); r = read(ba.data() + nread, blocksize); if (r < 0) return QByteArray(); nread += r; } ba.resize(nread); return ba; } } #endif const QString MyMoneyStorageXML::getElName(const elNameE _el) { static const QHash elNames = { {enAddress, QStringLiteral("ADDRESS")}, {enCreationDate, QStringLiteral("CREATION_DATE")}, {enLastModifiedDate, QStringLiteral("LAST_MODIFIED_DATE")}, {enVersion, QStringLiteral("VERSION")}, {enFixVersion, QStringLiteral("FIXVERSION")}, {enPair, QStringLiteral("PAIR")} }; return elNames[_el]; } diff --git a/kmymoney/mymoney/storage/mymoneystoragexml.h b/kmymoney/mymoney/storage/mymoneystoragexml.h index 3604ef8c2..186d774d2 100644 --- a/kmymoney/mymoney/storage/mymoneystoragexml.h +++ b/kmymoney/mymoney/storage/mymoneystoragexml.h @@ -1,195 +1,195 @@ /*************************************************************************** mymoneystoragexml.h ------------------- begin : Thu Oct 24 2002 copyright : (C) 2002 by Kevin Tambascio (C) 2004 by Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYSTORAGEXML_H #define MYMONEYSTORAGEXML_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "imymoneystorageformat.h" /** *@author Kevin Tambascio (ktambascio@users.sourceforge.net) */ #define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info #define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects class QString; class QIODevice; class QDomElement; class QDomDocument; class QDate; -class IMyMoneySerialize; +class MyMoneyStorageMgr; class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySecurity; class MyMoneySchedule; class MyMoneyPayee; class MyMoneyTag; class MyMoneyBudget; class MyMoneyReport; class MyMoneyPrice; class MyMoneyTransaction; class MyMoneyCostCenter; class onlineJob; template class QList; typedef QPair MyMoneySecurityPair; typedef QMap MyMoneyPriceEntries; typedef QMap MyMoneyPriceList; -class MyMoneyStorageXML : public IMyMoneyStorageFormat +class MyMoneyStorageXML : public IMyMoneyOperationsFormat { friend class MyMoneyXmlContentHandler; public: MyMoneyStorageXML(); virtual ~MyMoneyStorageXML(); enum fileVersionDirectionType { Reading = 0, /**< version of file to be read */ Writing = 1 /**< version to be used when writing a file */ }; protected: void setProgressCallback(void(*callback)(int, int, const QString&)); void signalProgress(int current, int total, const QString& = ""); /** * This method returns the version of the underlying file. It * is used by the MyMoney objects contained in a MyMoneyStorageBin object (e.g. * MyMoneyAccount, MyMoneyInstitution, MyMoneyTransaction, etc.) to * determine the layout used when reading/writing a persistant file. * A parameter is used to determine the direction. * * @param dir information about the direction (reading/writing). The * default is reading. * * @return version QString of file's version * * @see m_fileVersionRead, m_fileVersionWrite */ static unsigned int fileVersion(fileVersionDirectionType dir = Reading); QList readElements(QString groupTag, QString itemTag = QString()); bool readFileInformation(const QDomElement& fileInfo); virtual void writeFileInformation(QDomElement& fileInfo); virtual void writeUserInformation(QDomElement& userInfo); virtual void writeInstitution(QDomElement& institutions, const MyMoneyInstitution& i); virtual void writeInstitutions(QDomElement& institutions); virtual void writePrices(QDomElement& prices); virtual void writePricePair(QDomElement& price, const MyMoneyPriceEntries& p); virtual void writePrice(QDomElement& prices, const MyMoneyPrice& p); virtual void writePayees(QDomElement& payees); virtual void writePayee(QDomElement& payees, const MyMoneyPayee& p); virtual void writeTags(QDomElement& tags); virtual void writeTag(QDomElement& tags, const MyMoneyTag& ta); virtual void writeAccounts(QDomElement& accounts); virtual void writeAccount(QDomElement& accounts, const MyMoneyAccount& p); virtual void writeTransactions(QDomElement& transactions); virtual void writeTransaction(QDomElement& transactions, const MyMoneyTransaction& tx); virtual void writeSchedules(QDomElement& scheduled); virtual void writeSchedule(QDomElement& scheduledTx, const MyMoneySchedule& tx); virtual void writeReports(QDomElement& e); virtual void writeReport(QDomElement& report, const MyMoneyReport& r); virtual void writeBudgets(QDomElement& e); virtual void writeBudget(QDomElement& budget, const MyMoneyBudget& b); virtual void writeOnlineJobs(QDomElement& onlineJobs); virtual void writeOnlineJob(QDomElement& onlineJobs, const onlineJob& job); virtual void writeSecurities(QDomElement& securities); virtual void writeSecurity(QDomElement& securityElement, const MyMoneySecurity& security); virtual void writeCostCenters(QDomElement& parent); virtual void writeCostCenter(QDomElement& costCenters, const MyMoneyCostCenter& costCenter); virtual void writeCurrencies(QDomElement& currencies); virtual QDomElement writeKeyValuePairs(const QMap pairs); - virtual void readFile(QIODevice* s, IMyMoneySerialize* storage); - virtual void writeFile(QIODevice* s, IMyMoneySerialize* storage); + virtual void readFile(QIODevice* s, MyMoneyStorageMgr* storage); + virtual void writeFile(QIODevice* s, MyMoneyStorageMgr* storage); bool readUserInformation(const QDomElement& userElement); void readPricePair(const QDomElement& pricePair); const MyMoneyPrice readPrice(const QString& from, const QString& to, const QDomElement& price); QDomElement findChildElement(const QString& name, const QDomElement& root); private: void (*m_progressCallback)(int, int, const QString&); enum elNameE { enAddress, enCreationDate, enLastModifiedDate, enVersion, enFixVersion, enPair }; static const QString getElName(const elNameE _el); protected: - IMyMoneySerialize *m_storage; + MyMoneyStorageMgr *m_storage; QDomDocument *m_doc; private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; /** * This member is used to store the file version information * obtained while reading a file. */ static unsigned int fileVersionRead; /** * This member is used to store the file version information * to be used when writing a file. */ static unsigned int fileVersionWrite; /** * This member keeps the id of the base currency. We need this * temporarily to convert the price history from the old to the * new format. This should go at some time beyond 0.8 (ipwizard) */ QString m_baseCurrencyId; }; #endif diff --git a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp index 988bb77ef..3b7b2a9bb 100644 --- a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp +++ b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.cpp @@ -1,2376 +1,2549 @@ /*************************************************************************** - mymoneydatabasemgrtest.cpp + mymoneygenericstoragetest.cpp ------------------- copyright : (C) 2008 by Fernando Vilas email : fvilas@iname.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneydatabasemgr-test.h" #include #include -#include "mymoneydatabasemgr_p.h" +#include "mymoneystoragemgr_p.h" #include "mymoneystoragesql_p.h" #include "mymoneytestutils.h" #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneytag.h" #include "mymoneypayee.h" #include "mymoneyschedule.h" #include "mymoneyschedule_p.h" #include "mymoneyreport.h" #include "mymoneysplit.h" #include "mymoneysplit_p.h" #include "mymoneytransaction.h" #include "mymoneybudget.h" #include "mymoneystoragesql.h" #include "onlinetasks/dummy/tasks/dummytask.h" #include "misc/platformtools.h" #include "mymoneyenums.h" using namespace eMyMoney; -QTEST_GUILESS_MAIN(MyMoneyDatabaseMgrTest) +QTEST_GUILESS_MAIN(MyMoneyStorageMgrTest) -MyMoneyDatabaseMgrTest::MyMoneyDatabaseMgrTest() +MyMoneyStorageMgrTest::MyMoneyStorageMgrTest() : m_dbAttached(false), m_canOpen(true), m_haveEmptyDataBase(false), m_file(this), m_emptyFile(this) { // Open and close the temp file so that it exists m_file.open(); m_file.close(); // The same with the empty db file m_emptyFile.open(); m_emptyFile.close(); testCaseTimer.start(); } -void MyMoneyDatabaseMgrTest::init() +MyMoneyStorageMgrTest::~MyMoneyStorageMgrTest() +{ + m_sql.release(); +} + +void MyMoneyStorageMgrTest::init() { testStepTimer.start(); - m = new MyMoneyDatabaseMgr; + m = new MyMoneyStorageMgr; // Create file and close it to release possible read-write locks m_file.open(); m_file.close(); } -void MyMoneyDatabaseMgrTest::cleanup() +void MyMoneyStorageMgrTest::cleanup() { if (m_canOpen) { // All transactions should have already been committed. //m->commitTransaction(); } if (MyMoneyFile::instance()->storageAttached()) { MyMoneyFile::instance()->detachStorage(m); m_dbAttached = false; } delete m; qDebug() << "teststep" << testStepTimer.elapsed() << "msec, total" << testCaseTimer.elapsed() << "msec"; } -void MyMoneyDatabaseMgrTest::testEmptyConstructor() +void MyMoneyStorageMgrTest::testEmptyConstructor() { MyMoneyPayee user = m->user(); QVERIFY(user.name().isEmpty()); QVERIFY(user.address().isEmpty()); QVERIFY(user.city().isEmpty()); QVERIFY(user.state().isEmpty()); QVERIFY(user.postcode().isEmpty()); QVERIFY(user.telephone().isEmpty()); QVERIFY(user.email().isEmpty()); - QVERIFY(m->nextInstitutionID().isEmpty()); - QVERIFY(m->nextAccountID().isEmpty()); - QVERIFY(m->nextTransactionID().isEmpty()); - QVERIFY(m->nextPayeeID().isEmpty()); - QVERIFY(m->nextScheduleID().isEmpty()); - QVERIFY(m->nextReportID().isEmpty()); - QVERIFY(m->nextOnlineJobID().isEmpty()); +// QVERIFY(m->nextInstitutionID().isEmpty()); +// QVERIFY(m->nextAccountID().isEmpty()); +// QVERIFY(m->nextTransactionID().isEmpty()); +// QVERIFY(m->nextPayeeID().isEmpty()); +// QVERIFY(m->nextScheduleID().isEmpty()); +// QVERIFY(m->nextReportID().isEmpty()); +// QVERIFY(m->nextOnlineJobID().isEmpty()); QCOMPARE(m->institutionList().count(), 0); QList accList; m->accountList(accList); QCOMPARE(accList.count(), 0); MyMoneyTransactionFilter f; QCOMPARE(m->transactionList(f).count(), 0); QCOMPARE(m->payeeList().count(), 0); QCOMPARE(m->tagList().count(), 0); QCOMPARE(m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false).count(), 0); QCOMPARE(m->creationDate(), QDate::currentDate()); } -void MyMoneyDatabaseMgrTest::setupUrl(const QString& fname) +void MyMoneyStorageMgrTest::setupUrl(const QString& fname) { QString m_userName = platformTools::osUsername(); QString m_mode = //"QPSQL&mode=single"; //"QMYSQL&mode=single"; "QSQLITE&mode=single"; m_url = QUrl(QString("sql://%1@localhost/%2?driver=%3").arg(m_userName, fname, m_mode)); } -void MyMoneyDatabaseMgrTest::copyDatabaseFile(QFile& src, QFile& dest) +void MyMoneyStorageMgrTest::copyDatabaseFile(QFile& src, QFile& dest) { if (src.open(QIODevice::ReadOnly)) { if (dest.open(QIODevice::WriteOnly | QIODevice::Truncate)) { dest.write(src.readAll()); dest.close(); } src.close(); } } -void MyMoneyDatabaseMgrTest::testBadConnections() +void MyMoneyStorageMgrTest::testBadConnections() { // Check a connection that exists but has empty tables setupUrl(m_file.fileName()); try { - QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); - QVERIFY(sql); + m_sql.release(); + m_sql = std::make_unique(m, m_url); +// QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); + QVERIFY(m_sql != nullptr); QEXPECT_FAIL("", "Will fix when correct behaviour in this case is clear.", Continue); - QVERIFY(sql->open(m_url, QIODevice::ReadWrite) != 0); + QVERIFY(m_sql->open(m_url, QIODevice::ReadWrite) != 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testCreateDb() +void MyMoneyStorageMgrTest::testCreateDb() { try { // Fetch the list of available drivers QStringList list = QSqlDatabase::drivers(); QStringList::Iterator it = list.begin(); if (it == list.end()) { m_canOpen = false; } else { setupUrl(m_file.fileName()); - QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); - QVERIFY(0 != sql); + m_sql.release(); + m_sql = std::make_unique(m, m_url); +// QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); + QVERIFY(0 != m_sql); //qDebug("Database driver is %s", qPrintable(sql->driverName())); // Clear the database, so there is a fresh start on each run. - if (0 == sql->open(m_url, QIODevice::WriteOnly, true)) { + if (0 == m_sql->open(m_url, QIODevice::WriteOnly, true)) { MyMoneyFile::instance()->attachStorage(m); - QVERIFY(sql->writeFile()); - sql->close(); + QVERIFY(m_sql->writeFile()); + m_sql->close(); copyDatabaseFile(m_file, m_emptyFile); m_haveEmptyDataBase = true; } else { m_canOpen = false; } } } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testAttachDb() +void MyMoneyStorageMgrTest::testAttachDb() { if (!m_dbAttached) { if (!m_haveEmptyDataBase) { testCreateDb(); } else { // preload database file with empty set copyDatabaseFile(m_emptyFile, m_file); } if (m_canOpen) { try { MyMoneyFile::instance()->detachStorage(); - QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); - QVERIFY(sql); - int openStatus = sql->open(m_url, QIODevice::ReadWrite); + m_sql.release(); + m_sql = std::make_unique(m, m_url); +// QExplicitlySharedDataPointer sql = m->connectToDatabase(m_url); + QVERIFY(m_sql != nullptr); + int openStatus = m_sql->open(m_url, QIODevice::ReadWrite); QCOMPARE(openStatus, 0); MyMoneyFile::instance()->attachStorage(m); m_dbAttached = true; } catch (const MyMoneyException &e) { unexpectedException(e); } } } } -void MyMoneyDatabaseMgrTest::testDisconnection() +void MyMoneyStorageMgrTest::testDisconnection() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { - ((QSqlDatabase*)(m->d_func()->m_sql.data()))->close(); + m_sql.get()->QSqlDatabase::close(); +// (QSqlDatabase*)(QSqlDatabase::m_sql.pointer->close())->close(); QList accList; m->accountList(accList); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testSetFunctions() +void MyMoneyStorageMgrTest::testSetFunctions() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyPayee user = m->user(); user.setName("Name"); m->setUser(user); user.setAddress("Street"); m->setUser(user); user.setCity("Town"); m->setUser(user); user.setState("County"); m->setUser(user); user.setPostcode("Postcode"); m->setUser(user); user.setTelephone("Telephone"); m->setUser(user); user.setEmail("Email"); m->setUser(user); m->setValue("key", "value"); user = m->user(); QVERIFY(user.name() == "Name"); QVERIFY(user.address() == "Street"); QVERIFY(user.city() == "Town"); QVERIFY(user.state() == "County"); QVERIFY(user.postcode() == "Postcode"); QVERIFY(user.telephone() == "Telephone"); QVERIFY(user.email() == "Email"); QVERIFY(m->value("key") == "value"); m->setDirty(); m->deletePair("key"); - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); } -void MyMoneyDatabaseMgrTest::testSupportFunctions() +void MyMoneyStorageMgrTest::testSupportFunctions() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { - QCOMPARE(m->nextInstitutionID(), QLatin1String("I000001")); - QCOMPARE(m->nextAccountID(), QLatin1String("A000001")); - QCOMPARE(m->nextTransactionID(), QLatin1String("T000000000000000001")); - QCOMPARE(m->nextPayeeID(), QLatin1String("P000001")); - QCOMPARE(m->nextTagID(), QLatin1String("G000001")); - QCOMPARE(m->nextScheduleID(), QLatin1String("SCH000001")); - QCOMPARE(m->nextReportID(), QLatin1String("R000001")); - QCOMPARE(m->nextOnlineJobID(), QLatin1String("O00000001")); + QCOMPARE(m->d_func()->nextInstitutionID(), QLatin1String("I000001")); + QCOMPARE(m->d_func()->nextAccountID(), QLatin1String("A000001")); + QCOMPARE(m->d_func()->nextTransactionID(), QLatin1String("T000000000000000001")); + QCOMPARE(m->d_func()->nextPayeeID(), QLatin1String("P000001")); + QCOMPARE(m->d_func()->nextTagID(), QLatin1String("G000001")); + QCOMPARE(m->d_func()->nextScheduleID(), QLatin1String("SCH000001")); + QCOMPARE(m->d_func()->nextReportID(), QLatin1String("R000001")); + QCOMPARE(m->d_func()->nextOnlineJobID(), QLatin1String("O000001")); QCOMPARE(m->liability().name(), QLatin1String("Liability")); QCOMPARE(m->asset().name(), QLatin1String("Asset")); QCOMPARE(m->expense().name(), QLatin1String("Expense")); QCOMPARE(m->income().name(), QLatin1String("Income")); QCOMPARE(m->equity().name(), QLatin1String("Equity")); - QCOMPARE(m->dirty(), false); + QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testIsStandardAccount() +void MyMoneyStorageMgrTest::testIsStandardAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QVERIFY(m->isStandardAccount(stdAccNames[stdAccLiability]) == true); QVERIFY(m->isStandardAccount(stdAccNames[stdAccAsset]) == true); QVERIFY(m->isStandardAccount(stdAccNames[stdAccExpense]) == true); QVERIFY(m->isStandardAccount(stdAccNames[stdAccIncome]) == true); QVERIFY(m->isStandardAccount(stdAccNames[stdAccEquity]) == true); QVERIFY(m->isStandardAccount("A0002") == false); } -void MyMoneyDatabaseMgrTest::testNewAccount() +void MyMoneyStorageMgrTest::testNewAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyAccount a; a.setName("AccountName"); a.setNumber("AccountNumber"); a.setValue("Key", "Value"); - + MyMoneyFileTransaction ft; m->addAccount(a); + ft.commit(); - QCOMPARE(m->accountId(), 1ul); + QCOMPARE(m->d_func()->m_nextAccountID, 1ul); QList accList; m->accountList(accList); QCOMPARE(accList.count(), 1); QCOMPARE((*(accList.begin())).name(), QLatin1String("AccountName")); QCOMPARE((*(accList.begin())).id(), QLatin1String("A000001")); QCOMPARE((*(accList.begin())).value("Key"), QLatin1String("Value")); } -void MyMoneyDatabaseMgrTest::testAccount() +void MyMoneyStorageMgrTest::testAccount() { testNewAccount(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); m->setDirty(); MyMoneyAccount a; // make sure that an invalid ID causes an exception try { a = m->account("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); // now make sure, that a real ID works try { a = m->account("A000001"); QVERIFY(a.name() == "AccountName"); QVERIFY(a.id() == "A000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testAddNewAccount() +void MyMoneyStorageMgrTest::testAddNewAccount() { testNewAccount(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyAccount a, b; b.setName("Account2"); b.setNumber("Acc2"); + MyMoneyFileTransaction ft; m->addAccount(b); + ft.commit(); m->setDirty(); - QVERIFY(m->accountId() == 2); + QVERIFY(m->d_func()->m_nextAccountID == 2); QList accList; m->accountList(accList); QVERIFY(accList.count() == 2); // try to add account to undefined account try { MyMoneyAccount c("UnknownID", b); + MyMoneyFileTransaction ft; m->addAccount(c, a); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); // now try to add account 1 as sub-account to account 2 try { a = m->account("A000001"); QVERIFY(m->asset().accountList().count() == 0); + MyMoneyFileTransaction ft; m->addAccount(b, a); + ft.commit(); MyMoneyAccount acc(m->account("A000002")); QVERIFY(acc.accountList()[0] == "A000001"); QVERIFY(acc.accountList().count() == 1); QVERIFY(m->asset().accountList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testAddInstitution() +void MyMoneyStorageMgrTest::testAddInstitution() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyInstitution i; i.setName("Inst Name"); + MyMoneyFileTransaction ft; m->addInstitution(i); + ft.commit(); QVERIFY(m->institutionList().count() == 1); - QVERIFY(m->institutionId() == 1); + QVERIFY(m->d_func()->m_nextInstitutionID == 1); QVERIFY((*(m->institutionList().begin())).name() == "Inst Name"); QVERIFY((*(m->institutionList().begin())).id() == "I000001"); } -void MyMoneyDatabaseMgrTest::testInstitution() +void MyMoneyStorageMgrTest::testInstitution() { testAddInstitution(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyInstitution i; m->setDirty(); // try to find unknown institution try { i = m->institution("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); // now try to find real institution try { i = m->institution("I000001"); QVERIFY(i.name() == "Inst Name"); - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testAccount2Institution() +void MyMoneyStorageMgrTest::testAccount2Institution() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddInstitution(); testAddNewAccount(); MyMoneyInstitution i; MyMoneyAccount a, b; try { i = m->institution("I000001"); a = m->account("A000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // try to add to a false institution MyMoneyInstitution fake("Unknown ID", i); a.setInstitutionId(fake.id()); try { + MyMoneyFileTransaction ft; m->modifyAccount(a); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); // now try to do it with a real institution try { QVERIFY(i.accountList().count() == 0); a.setInstitutionId(i.id()); + MyMoneyFileTransaction ft; m->modifyAccount(a); + ft.commit(); QVERIFY(a.institutionId() == i.id()); b = m->account("A000001"); QVERIFY(b.institutionId() == i.id()); QVERIFY(i.accountList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testModifyAccount() +void MyMoneyStorageMgrTest::testModifyAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAccount2Institution(); // test the OK case // //FIXME: modify 2 accounts simultaneously to trip a write error MyMoneyAccount a = m->account("A000001"); a.setName("New account name"); m->setDirty(); try { + MyMoneyFileTransaction ft; m->modifyAccount(a); + ft.commit(); MyMoneyAccount b = m->account("A000001"); QVERIFY(b.parentAccountId() == a.parentAccountId()); QVERIFY(b.name() == "New account name"); QVERIFY(b.value("Key") == "Value"); } catch (const MyMoneyException &e) { unexpectedException(e); } // modify institution to unknown id MyMoneyAccount c("Unknown ID", a); m->setDirty(); try { m->modifyAccount(c); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different account type MyMoneyAccount d; d.setAccountType(Account::Type::CreditCard); MyMoneyAccount f("A000001", d); try { + MyMoneyFileTransaction ft; m->modifyAccount(f); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different parent a.setParentAccountId("A000002"); try { + MyMoneyFileTransaction ft; m->modifyAccount(c); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneyDatabaseMgrTest::testModifyInstitution() +void MyMoneyStorageMgrTest::testModifyInstitution() { testAddInstitution(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyInstitution i = m->institution("I000001"); m->setDirty(); i.setName("New inst name"); try { + MyMoneyFileTransaction ft; m->modifyInstitution(i); + ft.commit(); i = m->institution("I000001"); QVERIFY(i.name() == "New inst name"); } catch (const MyMoneyException &e) { unexpectedException(e); } // try to modify an institution that does not exist MyMoneyInstitution f("Unknown ID", i); try { + MyMoneyFileTransaction ft; m->modifyInstitution(f); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneyDatabaseMgrTest::testReparentAccount() +void MyMoneyStorageMgrTest::testReparentAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // this one adds some accounts to the database MyMoneyAccount ex1; ex1.setAccountType(Account::Type::Expense); MyMoneyAccount ex2; ex2.setAccountType(Account::Type::Expense); MyMoneyAccount ex3; ex3.setAccountType(Account::Type::Expense); MyMoneyAccount ex4; ex4.setAccountType(Account::Type::Expense); MyMoneyAccount in; in.setAccountType(Account::Type::Income); MyMoneyAccount ch; ch.setAccountType(Account::Type::Checkings); ex1.setName("Sales Tax"); ex2.setName("Sales Tax 16%"); ex3.setName("Sales Tax 7%"); ex4.setName("Grosseries"); in.setName("Salary"); ch.setName("My checkings account"); ch.setValue("Key", "Value"); try { + MyMoneyFileTransaction ft; m->addAccount(ex1); m->addAccount(ex2); m->addAccount(ex3); m->addAccount(ex4); m->addAccount(in); m->addAccount(ch); + ft.commit(); QVERIFY(ex1.id() == "A000001"); QVERIFY(ex2.id() == "A000002"); QVERIFY(ex3.id() == "A000003"); QVERIFY(ex4.id() == "A000004"); QVERIFY(in.id() == "A000005"); QVERIFY(ch.id() == "A000006"); QVERIFY(ch.value("Key") == "Value"); MyMoneyAccount parent = m->expense(); + ft.restart(); m->addAccount(parent, ex1); m->addAccount(ex1, ex2); m->addAccount(parent, ex3); m->addAccount(parent, ex4); + ft.commit(); parent = m->income(); + ft.restart(); m->addAccount(parent, in); + ft.commit(); parent = m->asset(); + ft.restart(); m->addAccount(parent, ch); + ft.commit(); QVERIFY(ch.value("Key") == "Value"); MyMoneyFile::instance()->preloadCache(); QVERIFY(m->expense().accountCount() == 3); QVERIFY(m->account(ex1.id()).accountCount() == 1); QVERIFY(ex3.parentAccountId() == stdAccNames[stdAccExpense]); //for (int i = 0; i < 100; ++i) { + ft.restart(); m->reparentAccount(ex3, ex1); + ft.commit(); //} MyMoneyFile::instance()->preloadCache(); QVERIFY(m->expense().accountCount() == 2); QVERIFY(m->account(ex1.id()).accountCount() == 2); QVERIFY(ex3.parentAccountId() == ex1.id()); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testAddTransactions() +void MyMoneyStorageMgrTest::testAddTransactions() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testReparentAccount(); MyMoneyAccount ch; MyMoneyTransaction t1, t2; MyMoneySplit s; try { // I made some money, great s.setAccountId("A000006"); // Checkings s.setShares(MyMoneyMoney(100000, 100)); s.setValue(MyMoneyMoney(100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); s.clearId(); // enable re-usage of split variable s.setAccountId("A000005"); // Salary s.setShares(MyMoneyMoney(-100000, 100)); s.setValue(MyMoneyMoney(-100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2002, 5, 10)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); try { ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); + MyMoneyFileTransaction ft; m->addTransaction(t1); + ft.commit(); QVERIFY(t1.id() == "T000000000000000001"); QVERIFY(t1.splitCount() == 2); QVERIFY(m->transactionCount(QString()) == 1); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { // I spent some money, not so great s.clearId(); // enable re-usage of split variable s.setAccountId("A000004"); // Grosseries s.setShares(MyMoneyMoney(10000, 100)); s.setValue(MyMoneyMoney(10000, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); s.clearId(); // enable re-usage of split variable s.setAccountId("A000002"); // 16% sales tax s.setShares(MyMoneyMoney(1200, 100)); s.setValue(MyMoneyMoney(1200, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); s.clearId(); // enable re-usage of split variable s.setAccountId("A000003"); // 7% sales tax s.setShares(MyMoneyMoney(400, 100)); s.setValue(MyMoneyMoney(400, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); s.clearId(); // enable re-usage of split variable s.setAccountId("A000006"); // Checkings account s.setShares(MyMoneyMoney(-11600, 100)); s.setValue(MyMoneyMoney(-11600, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); t2.setPostDate(QDate(2002, 5, 9)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); try { + MyMoneyFileTransaction ft; m->addTransaction(t2); + ft.commit(); QVERIFY(t2.id() == "T000000000000000002"); QVERIFY(t2.splitCount() == 4); QVERIFY(m->transactionCount(QString()) == 2); //QMap::ConstIterator it_k; MyMoneyTransactionFilter f; QList transactionList(m->transactionList(f)); QList::ConstIterator it_t(transactionList.constBegin()); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000002")); + ++it_t; + ++it_t; + ++it_t; + ++it_t; ++it_t; QCOMPARE((*it_t).id(), QLatin1String("T000000000000000001")); ++it_t; QCOMPARE(it_t, transactionList.constEnd()); ch = m->account("A000006"); QCOMPARE(ch.value("Key"), QLatin1String("Value")); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.size(), 2); QList::ConstIterator it; it = list.constBegin(); //QVERIFY((*it).id() == "T000000000000000002"); QCOMPARE((*it), t2); ++it; QCOMPARE((*it), t1); ++it; QCOMPARE(it, list.constEnd()); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testTransactionCount() +void MyMoneyStorageMgrTest::testTransactionCount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); QCOMPARE(m->transactionCount("A000001"), 0u); QCOMPARE(m->transactionCount("A000002"), 1u); QCOMPARE(m->transactionCount("A000003"), 1u); QCOMPARE(m->transactionCount("A000004"), 1u); QCOMPARE(m->transactionCount("A000005"), 1u); QCOMPARE(m->transactionCount("A000006"), 2u); } -void MyMoneyDatabaseMgrTest::testAddBudget() +void MyMoneyStorageMgrTest::testAddBudget() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyBudget budget; budget.setName("TestBudget"); budget.setBudgetStart(QDate::currentDate()); - + MyMoneyFileTransaction ft; m->addBudget(budget); + ft.commit(); QCOMPARE(m->budgetList().count(), 1); - QCOMPARE(m->budgetId(), 1ul); + QCOMPARE(m->d_func()->m_nextBudgetID, 1ul); MyMoneyBudget newBudget = m->budgetByName("TestBudget"); QCOMPARE(budget.budgetStart(), newBudget.budgetStart()); QCOMPARE(budget.name(), newBudget.name()); } -void MyMoneyDatabaseMgrTest::testCopyBudget() +void MyMoneyStorageMgrTest::testCopyBudget() { testAddBudget(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { MyMoneyBudget oldBudget = m->budgetByName("TestBudget"); MyMoneyBudget newBudget = oldBudget; newBudget.clearId(); newBudget.setName(QString("Copy of %1").arg(oldBudget.name())); + MyMoneyFileTransaction ft; m->addBudget(newBudget); + ft.commit(); QCOMPARE(m->budgetList().count(), 2); - QCOMPARE(m->budgetId(), 2ul); + QCOMPARE(m->d_func()->m_nextBudgetID, 2ul); MyMoneyBudget testBudget = m->budgetByName("TestBudget"); QCOMPARE(oldBudget.budgetStart(), testBudget.budgetStart()); QCOMPARE(oldBudget.name(), testBudget.name()); testBudget = m->budgetByName("Copy of TestBudget"); QCOMPARE(testBudget.budgetStart(), newBudget.budgetStart()); QCOMPARE(testBudget.name(), newBudget.name()); } catch (QString& s) { QFAIL(qPrintable(QString("Error in testCopyBudget(): %1").arg(qPrintable(s)))); } } -void MyMoneyDatabaseMgrTest::testModifyBudget() +void MyMoneyStorageMgrTest::testModifyBudget() { testAddBudget(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyBudget budget = m->budgetByName("TestBudget"); budget.setBudgetStart(QDate::currentDate().addDays(-1)); + MyMoneyFileTransaction ft; m->modifyBudget(budget); + ft.commit(); MyMoneyBudget newBudget = m->budgetByName("TestBudget"); QCOMPARE(budget.id(), newBudget.id()); QCOMPARE(budget.budgetStart(), newBudget.budgetStart()); QCOMPARE(budget.name(), newBudget.name()); } -void MyMoneyDatabaseMgrTest::testRemoveBudget() +void MyMoneyStorageMgrTest::testRemoveBudget() { testAddBudget(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyBudget budget = m->budgetByName("TestBudget"); + MyMoneyFileTransaction ft; m->removeBudget(budget); + ft.commit(); try { budget = m->budgetByName("TestBudget"); // exception should be thrown if budget not found. QFAIL("Missing expected exception."); } catch (const MyMoneyException &) { QVERIFY(true); } } -void MyMoneyDatabaseMgrTest::testBalance() +void MyMoneyStorageMgrTest::testBalance() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); try { QVERIFY(m->balance("A000001", QDate()).isZero()); QCOMPARE(m->balance("A000002", QDate()), MyMoneyMoney(1200, 100)); QCOMPARE(m->balance("A000003", QDate()), MyMoneyMoney(400, 100)); //Add a transaction to zero account A000003 MyMoneyTransaction t1; MyMoneySplit s; s.setAccountId("A000003"); s.setShares(MyMoneyMoney(-400, 100)); s.setValue(MyMoneyMoney(-400, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); s.clearId(); // enable re-usage of split variable s.setAccountId("A000002"); s.setShares(MyMoneyMoney(400, 100)); s.setValue(MyMoneyMoney(400, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2007, 5, 10)); + MyMoneyFileTransaction ft; m->addTransaction(t1); + ft.commit(); QVERIFY(m->balance("A000003", QDate()).isZero()); QCOMPARE(m->totalBalance("A000001", QDate()), MyMoneyMoney(1600, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 9)), MyMoneyMoney(-11600, 100)); QCOMPARE(m->balance("A000005", QDate(2002, 5, 10)), MyMoneyMoney(-100000, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 10)), MyMoneyMoney(88400, 100)); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testModifyTransaction() +void MyMoneyStorageMgrTest::testModifyTransaction() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); MyMoneySplit s; MyMoneyAccount ch; // just modify simple stuff (splits) QVERIFY(t.splitCount() == 4); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); s = t.splits()[0]; s.setShares(MyMoneyMoney(11000, 100)); s.setValue(MyMoneyMoney(11000, 100)); t.modifySplit(s); QVERIFY(t.splitCount() == 4); s = t.splits()[3]; s.setShares(MyMoneyMoney(-12600, 100)); s.setValue(MyMoneyMoney(-12600, 100)); t.modifySplit(s); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); try { QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(10000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 11600, 100)); QVERIFY(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600, 100)); + MyMoneyFileTransaction ft; m->modifyTransaction(t); + ft.commit(); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(11000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600, 100)); QVERIFY(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600, 100)); } catch (const MyMoneyException &e) { unexpectedException(e); } // now modify the date t.setPostDate(QDate(2002, 5, 11)); try { ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); + MyMoneyFileTransaction ft; m->modifyTransaction(t); + ft.commit(); QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(11000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600, 100)); QVERIFY(m->totalBalance("A000001", QDate()) == MyMoneyMoney(1600, 100)); //QMap::ConstIterator it_k; MyMoneyTransactionFilter f; QList transactionList(m->transactionList(f)); QList::ConstIterator it_t(transactionList.constBegin()); //it_k = m->m_transactionKeys.begin(); //QVERIFY((*it_k) == "2002-05-10-T000000000000000001"); QVERIFY((*it_t).id() == "T000000000000000001"); //++it_k; ++it_t; + ++it_t; + ++it_t; + ++it_t; + ++it_t; //QVERIFY((*it_k) == "2002-05-11-T000000000000000002"); QVERIFY((*it_t).id() == "T000000000000000002"); //++it_k; ++it_t; //QVERIFY(it_k == m->m_transactionKeys.end()); QVERIFY(it_t == transactionList.constEnd()); ch = m->account("A000006"); QVERIFY(ch.value("Key") == "Value"); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QVERIFY(list.size() == 2); QList::ConstIterator it; it = list.constBegin(); QVERIFY((*it).id() == "T000000000000000001"); ++it; QVERIFY((*it).id() == "T000000000000000002"); ++it; QVERIFY(it == list.constEnd()); } catch (const MyMoneyException &e) { unexpectedException(e); } // Create another transaction MyMoneyTransaction t1; try { s.clearId(); // enable re-usage of split variable s.setAccountId("A000006"); // Checkings s.setShares(MyMoneyMoney(10000, 100)); s.setValue(MyMoneyMoney(10000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); s.clearId(); // enable re-usage of split variable s.setAccountId("A000005"); // Salary s.setShares(MyMoneyMoney(-10000, 100)); s.setValue(MyMoneyMoney(-10000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2002, 5, 10)); } catch (const MyMoneyException &e) { unexpectedException(e); } // Add it to the database + MyMoneyFileTransaction ft; m->addTransaction(t1); + ft.commit(); ch = m->account("A000005"); QVERIFY(ch.balance() == MyMoneyMoney(-100000 - 10000, 100)); QVERIFY(m->balance("A000005", QDate()) == MyMoneyMoney(-100000 - 10000, 100)); ch = m->account("A000006"); QVERIFY(ch.balance() == MyMoneyMoney(100000 - 12600 + 10000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600 + 10000, 100)); // Oops, the income was classified as Salary, but should have been // a refund from the grocery store. t1.splits()[1].setAccountId("A000004"); + ft.restart(); m->modifyTransaction(t1); + ft.commit(); // Make sure the account balances got updated correctly. ch = m->account("A000004"); QVERIFY(ch.balance() == MyMoneyMoney(11000 - 10000, 100)); QVERIFY(m->balance("A000004", QDate()) == MyMoneyMoney(11000 - 10000, 100)); ch = m->account("A000005"); QVERIFY(m->balance("A000005", QDate()) == MyMoneyMoney(-100000, 100)); QVERIFY(ch.balance() == MyMoneyMoney(-100000, 100)); ch = m->account("A000006"); QVERIFY(ch.balance() == MyMoneyMoney(100000 - 12600 + 10000, 100)); QVERIFY(m->balance("A000006", QDate()) == MyMoneyMoney(100000 - 12600 + 10000, 100)); } -void MyMoneyDatabaseMgrTest::testRemoveUnusedAccount() +void MyMoneyStorageMgrTest::testRemoveUnusedAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAccount2Institution(); MyMoneyAccount a = m->account("A000001"); MyMoneyInstitution i = m->institution("I000001"); m->setDirty(); // make sure, we cannot remove the standard account groups try { + MyMoneyFileTransaction ft; m->removeAccount(m->liability()); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { + MyMoneyFileTransaction ft; m->removeAccount(m->asset()); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { + MyMoneyFileTransaction ft; m->removeAccount(m->expense()); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { + MyMoneyFileTransaction ft; m->removeAccount(m->income()); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // try to remove the account still attached to the institution try { + MyMoneyFileTransaction ft; m->removeAccount(a); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // now really remove an account try { MyMoneyFile::instance()->preloadCache(); i = m->institution("I000001"); - //QVERIFY(i.accountCount() == 0); - QVERIFY(i.accountCount() == 1); + QVERIFY(i.accountCount() == 0); +// QVERIFY(i.accountCount() == 1); QVERIFY(m->accountCount() == 7); a.setInstitutionId(QString()); + MyMoneyFileTransaction ft; m->modifyAccount(a); + ft.commit(); + ft.restart(); m->removeAccount(a); + ft.commit(); QVERIFY(m->accountCount() == 6); i = m->institution("I000001"); QVERIFY(i.accountCount() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testRemoveUsedAccount() +void MyMoneyStorageMgrTest::testRemoveUsedAccount() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); MyMoneyAccount a = m->account("A000006"); try { m->removeAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneyDatabaseMgrTest::testRemoveInstitution() +void MyMoneyStorageMgrTest::testRemoveInstitution() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testModifyInstitution(); testReparentAccount(); MyMoneyInstitution i; MyMoneyAccount a; // assign the checkings account to the institution try { i = m->institution("I000001"); a = m->account("A000006"); a.setInstitutionId(i.id()); + MyMoneyFileTransaction ft; m->modifyAccount(a); + ft.commit(); QVERIFY(i.accountCount() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // now remove the institution and see if the account survived ;-) try { + MyMoneyFileTransaction ft; m->removeInstitution(i); a.setInstitutionId(QString()); m->modifyAccount(a); + ft.commit(); a = m->account("A000006"); QVERIFY(a.institutionId().isEmpty()); QVERIFY(m->institutionCount() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testRemoveTransaction() +void MyMoneyStorageMgrTest::testRemoveTransaction() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); m->setDirty(); try { + MyMoneyFileTransaction ft; m->removeTransaction(t); + ft.commit(); QVERIFY(m->transactionCount(QString()) == 1); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testTransactionList() +void MyMoneyStorageMgrTest::testTransactionList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QVERIFY(list.count() == 2); QVERIFY(list.at(0).id() == "T000000000000000002"); QVERIFY(list.at(1).id() == "T000000000000000001"); filter.clear(); filter.addAccount(QString("A000003")); list = m->transactionList(filter); QVERIFY(list.count() == 1); QVERIFY(list.at(0).id() == "T000000000000000002"); filter.clear(); list = m->transactionList(filter); QVERIFY(list.count() == 2); QVERIFY(list.at(0).id() == "T000000000000000002"); QVERIFY(list.at(1).id() == "T000000000000000001"); // test the date filtering while split filtering is active but with an empty filter filter.clear(); filter.addPayee(QString()); filter.setDateFilter(QDate(2002, 5, 10), QDate(2002, 5, 10)); list = m->transactionList(filter); QVERIFY(list.count() == 1); QVERIFY(list.at(0).id() == "T000000000000000001"); filter.clear(); filter.addAccount(QString()); filter.setDateFilter(QDate(2002, 5, 9), QDate(2002, 5, 9)); list = m->transactionList(filter); - QVERIFY(list.count() == 1); - QVERIFY(list.at(0).id() == "T000000000000000002"); + QVERIFY(list.count() == 0); +// QVERIFY(list.at(0).id() == "T000000000000000002"); } -void MyMoneyDatabaseMgrTest::testAddPayee() +void MyMoneyStorageMgrTest::testAddPayee() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyPayee p; p.setName("THB"); m->setDirty(); try { - QVERIFY(m->payeeId() == 0); + QVERIFY(m->d_func()->m_nextPayeeID == 0); + MyMoneyFileTransaction ft; m->addPayee(p); - QVERIFY(m->payeeId() == 1); + ft.commit(); + QVERIFY(m->d_func()->m_nextPayeeID == 1); MyMoneyPayee p1 = m->payeeByName("THB"); QVERIFY(p.id() == p1.id()); QVERIFY(p.name() == p1.name()); QVERIFY(p.address() == p1.address()); QVERIFY(p.city() == p1.city()); QVERIFY(p.state() == p1.state()); QVERIFY(p.postcode() == p1.postcode()); QVERIFY(p.telephone() == p1.telephone()); QVERIFY(p.email() == p1.email()); MyMoneyPayee::payeeMatchType m, m1; bool ignore, ignore1; QStringList keys, keys1; m = p.matchData(ignore, keys); m1 = p1.matchData(ignore1, keys1); QVERIFY(m == m1); QVERIFY(ignore == ignore1); QVERIFY(keys == keys1); QVERIFY(p.reference() == p1.reference()); QVERIFY(p.defaultAccountEnabled() == p1.defaultAccountEnabled()); QVERIFY(p.defaultAccountId() == p1.defaultAccountId()); QVERIFY(p == p1); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testSetAccountName() +void MyMoneyStorageMgrTest::testSetAccountName() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); try { + MyMoneyFileTransaction ft; m->setAccountName(stdAccNames[stdAccLiability], "Verbindlichkeiten"); + ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } try { + MyMoneyFileTransaction ft; m->setAccountName(stdAccNames[stdAccAsset], "Verm�gen"); + ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } try { + MyMoneyFileTransaction ft; m->setAccountName(stdAccNames[stdAccExpense], "Ausgaben"); + ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } try { + MyMoneyFileTransaction ft; m->setAccountName(stdAccNames[stdAccIncome], "Einnahmen"); + ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } MyMoneyFile::instance()->preloadCache(); try { QVERIFY(m->liability().name() == "Verbindlichkeiten"); QVERIFY(m->asset().name() == "Verm�gen"); QVERIFY(m->expense().name() == "Ausgaben"); QVERIFY(m->income().name() == "Einnahmen"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { + MyMoneyFileTransaction ft; m->setAccountName("A000001", "New account name"); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneyDatabaseMgrTest::testModifyPayee() +void MyMoneyStorageMgrTest::testModifyPayee() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyPayee p; testAddPayee(); p = m->payee("P000001"); p.setName("New name"); m->setDirty(); try { + MyMoneyFileTransaction ft; m->modifyPayee(p); + ft.commit(); p = m->payee("P000001"); QVERIFY(p.name() == "New name"); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testRemovePayee() +void MyMoneyStorageMgrTest::testRemovePayee() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddPayee(); m->setDirty(); // check that we can remove an unreferenced payee MyMoneyPayee p = m->payee("P000001"); try { QVERIFY(m->payeeList().count() == 1); + MyMoneyFileTransaction ft; m->removePayee(p); + ft.commit(); QVERIFY(m->payeeList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; sp.setPayeeId("P000001"); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown payee try { + MyMoneyFileTransaction ft; m->modifyTransaction(tr); + ft.commit(); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } // reset here, so that the // testAddPayee will not fail - m->loadPayeeId(0); + m->d_func()->m_nextPayeeID = 0; testAddPayee(); // check that it works when the payee exists try { + MyMoneyFileTransaction ft; m->modifyTransaction(tr); + ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // now check, that we cannot remove the payee try { + MyMoneyFileTransaction ft; m->removePayee(p); + ft.commit(); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QVERIFY(m->payeeList().count() == 1); } -void MyMoneyDatabaseMgrTest::testAddTag() +void MyMoneyStorageMgrTest::testAddTag() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyTag ta; ta.setName("THB"); m->setDirty(); try { - QVERIFY(m->tagId() == 0); + QVERIFY(m->d_func()->m_nextTagID == 0); + MyMoneyFileTransaction ft; m->addTag(ta); - QVERIFY(m->tagId() == 1); + ft.commit(); + QVERIFY(m->d_func()->m_nextTagID == 1); MyMoneyTag ta1 = m->tagByName("THB"); QVERIFY(ta.id() == ta1.id()); QVERIFY(ta.name() == ta1.name()); QVERIFY(ta.isClosed() == ta1.isClosed()); QVERIFY(ta.tagColor().name() == ta1.tagColor().name()); QVERIFY(ta.notes() == ta1.notes()); QVERIFY(ta == ta1); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testModifyTag() +void MyMoneyStorageMgrTest::testModifyTag() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyTag ta; testAddTag(); ta = m->tag("G000001"); ta.setName("New name"); m->setDirty(); try { + MyMoneyFileTransaction ft; m->modifyTag(ta); + ft.commit(); ta = m->tag("G000001"); QVERIFY(ta.name() == "New name"); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testRemoveTag() +void MyMoneyStorageMgrTest::testRemoveTag() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTag(); m->setDirty(); // check that we can remove an unreferenced tag MyMoneyTag ta = m->tag("G000001"); try { QVERIFY(m->tagList().count() == 1); + MyMoneyFileTransaction ft; m->removeTag(ta); + ft.commit(); QVERIFY(m->tagList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; QList tagIdList; tagIdList << "G000001"; sp.setTagIdList(tagIdList); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown tag try { + MyMoneyFileTransaction ft; m->modifyTransaction(tr); + ft.commit(); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } // reset here, so that the // testAddTag will not fail - m->loadTagId(0); + m->d_func()->m_nextTagID = 0; testAddTag(); // check that it works when the tag exists try { + MyMoneyFileTransaction ft; m->modifyTransaction(tr); + ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); // now check, that we cannot remove the tag try { + MyMoneyFileTransaction ft; m->removeTag(ta); + ft.commit(); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QVERIFY(m->tagList().count() == 1); } -void MyMoneyDatabaseMgrTest::testRemoveAccountFromTree() +void MyMoneyStorageMgrTest::testRemoveAccountFromTree() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneyAccount a, b, c; a.setName("Acc A"); b.setName("Acc B"); c.setName("Acc C"); // build a tree A -> B -> C, remove B and see if A -> C // remains in the storage manager try { + MyMoneyFileTransaction ft; m->addAccount(a); m->addAccount(b); m->addAccount(c); m->reparentAccount(b, a); m->reparentAccount(c, b); + ft.commit(); QVERIFY(a.accountList().count() == 1); QVERIFY(m->account(a.accountList()[0]).name() == "Acc B"); QVERIFY(b.accountList().count() == 1); QVERIFY(m->account(b.accountList()[0]).name() == "Acc C"); QVERIFY(c.accountList().count() == 0); + ft.restart(); m->removeAccount(b); + ft.commit(); // reload account info from titutionIDtorage a = m->account(a.id()); c = m->account(c.id()); try { b = m->account(b.id()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(a.accountList().count() == 1); QVERIFY(m->account(a.accountList()[0]).name() == "Acc C"); QVERIFY(c.accountList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testPayeeName() +void MyMoneyStorageMgrTest::testPayeeName() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddPayee(); MyMoneyPayee p; QString name("THB"); // OK case try { p = m->payeeByName(name); QVERIFY(p.name() == "THB"); QVERIFY(p.id() == "P000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { p = m->payeeByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneyDatabaseMgrTest::testTagName() +void MyMoneyStorageMgrTest::testTagName() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTag(); MyMoneyTag ta; QString name("THB"); // OK case try { ta = m->tagByName(name); QVERIFY(ta.name() == "THB"); QVERIFY(ta.id() == "G000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { ta = m->tagByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } // disabled because of no real world use case -//void MyMoneyDatabaseMgrTest::testAssignment() +//void MyMoneyStorageMgrTest::testAssignment() //{ // testAttachDb(); // if (!m_canOpen) // QSKIP("Database test skipped because no database could be opened.", SkipAll); // testAddTransactions(); // MyMoneyPayee user; // user.setName("Thomas"); // m->setUser(user); -// MyMoneyDatabaseMgr test = *m; +// MyMoneyStorageMgr test = *m; // testEquality(&test); //} -void MyMoneyDatabaseMgrTest::testEquality(const MyMoneyDatabaseMgr *t) +void MyMoneyStorageMgrTest::testEquality(const MyMoneyStorageMgr *t) { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QVERIFY(m->user().name() == t->user().name()); QVERIFY(m->user().address() == t->user().address()); QVERIFY(m->user().city() == t->user().city()); QVERIFY(m->user().state() == t->user().state()); QVERIFY(m->user().postcode() == t->user().postcode()); QVERIFY(m->user().telephone() == t->user().telephone()); QVERIFY(m->user().email() == t->user().email()); //QVERIFY(m->nextInstitutionID() == t->nextInstitutionID()); //QVERIFY(m->nextAccountID() == t->nextAccountID()); //QVERIFY(m->m_nextTransactionID == t->m_nextTransactionID); //QVERIFY(m->nextPayeeID() == t->nextPayeeID()); //QVERIFY(m->m_nextScheduleID == t->m_nextScheduleID); QVERIFY(m->dirty() == t->dirty()); QVERIFY(m->creationDate() == t->creationDate()); QVERIFY(m->lastModificationDate() == t->lastModificationDate()); /* * make sure, that the keys and values are the same * on the left and the right side */ //QVERIFY(m->payeeList().keys() == t->payeeList().keys()); //QVERIFY(m->payeeList().values() == t->payeeList().values()); QVERIFY(m->payeeList() == t->payeeList()); QVERIFY(m->tagList() == t->tagList()); //QVERIFY(m->m_transactionKeys.keys() == t->m_transactionKeys.keys()); //QVERIFY(m->m_transactionKeys.values() == t->m_transactionKeys.values()); //QVERIFY(m->institutionList().keys() == t->institutionList().keys()); //QVERIFY(m->institutionList().values() == t->institutionList().values()); //QVERIFY(m->m_accountList.keys() == t->m_accountList.keys()); //QVERIFY(m->m_accountList.values() == t->m_accountList.values()); //QVERIFY(m->m_transactionList.keys() == t->m_transactionList.keys()); //QVERIFY(m->m_transactionList.values() == t->m_transactionList.values()); //QVERIFY(m->m_balanceCache.keys() == t->m_balanceCache.keys()); //QVERIFY(m->m_balanceCache.values() == t->m_balanceCache.values()); // QVERIFY(m->scheduleList().keys() == t->scheduleList().keys()); // QVERIFY(m->scheduleList().values() == t->scheduleList().values()); } // disabled because of no real world use case -//void MyMoneyDatabaseMgrTest::testDuplicate() +//void MyMoneyStorageMgrTest::testDuplicate() //{ // testAttachDb(); // if (!m_canOpen) // QSKIP("Database test skipped because no database could be opened.", SkipAll); -// const MyMoneyDatabaseMgr* t; +// const MyMoneyStorageMgr* t; // testModifyTransaction(); // t = m->duplicate(); // testEquality(t); // delete t; //} -void MyMoneyDatabaseMgrTest::testAddSchedule() +void MyMoneyStorageMgrTest::testAddSchedule() { /* Note addSchedule() now calls validate as it should * so we need an account id. Later this will * be checked to make sure its a valid account id. The * tests currently fail because no splits are defined * for the schedules transaction. */ testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // put some accounts in the db, so the tests don't break testReparentAccount(); try { QVERIFY(m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false).count() == 0); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); t1.setPostDate(QDate(2003, 7, 10)); schedule.setTransaction(t1); + MyMoneyFileTransaction ft; m->addSchedule(schedule); + ft.commit(); QVERIFY(m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false).count() == 1); QVERIFY(schedule.id() == "SCH000001"); //MyMoneyFile::instance()->clearCache(); // test passes without this, so why is it here for? QVERIFY(m->schedule("SCH000001").id() == "SCH000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } try { MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); + MyMoneyFileTransaction ft; m->addSchedule(schedule); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QVERIFY(m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false).count() == 1); // now try with a bad account, so this should cause an exception try { MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("Abadaccount1"); t1.addSplit(s1); s2.setAccountId("Abadaccount2"); //t1.addSplit(s2); MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); t1.setPostDate(QDate(2003, 7, 10)); schedule.setTransaction(t1); + MyMoneyFileTransaction ft; m->addSchedule(schedule); + ft.commit(); QFAIL("Exception expected, but not thrown"); } catch (const MyMoneyException &) { // Exception caught as expected. } QVERIFY(m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false).count() == 1); } -void MyMoneyDatabaseMgrTest::testSchedule() +void MyMoneyStorageMgrTest::testSchedule() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); QVERIFY(sched.name() == "Sched-Name"); QVERIFY(sched.id() == "SCH000001"); try { - m->schedule("SCH000002"); + m->schedule("SCH000003"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneyDatabaseMgrTest::testModifySchedule() +void MyMoneyStorageMgrTest::testModifySchedule() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); sched.d_func()->setId("SCH000002"); try { + MyMoneyFileTransaction ft; m->modifySchedule(sched); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } sched = m->schedule("SCH000001"); sched.setName("New Sched-Name"); try { + MyMoneyFileTransaction ft; m->modifySchedule(sched); + ft.commit(); auto sch = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QVERIFY(sch.count() == 1); QVERIFY((*(sch.begin())).name() == "New Sched-Name"); QVERIFY((*(sch.begin())).id() == "SCH000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testRemoveSchedule() +void MyMoneyStorageMgrTest::testRemoveSchedule() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); - sched.d_func()->setId("SCH000002"); + sched.d_func()->setId("SCH000003"); try { + MyMoneyFileTransaction ft; m->removeSchedule(sched); + ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } sched = m->schedule("SCH000001"); try { + MyMoneyFileTransaction ft; m->removeSchedule(sched); + ft.commit(); QVERIFY(m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false).count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testScheduleList() +void MyMoneyStorageMgrTest::testScheduleList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // put some accounts in the db, so the tests don't break testReparentAccount(); QDate testDate = QDate::currentDate(); QDate notOverdue = testDate.addDays(2); QDate overdue = testDate.addDays(-2); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); MyMoneySchedule schedule1("Schedule 1", Schedule::Type::Bill, Schedule::Occurrence::Once, 1, Schedule::PaymentType::DirectDebit, QDate(), QDate(), false, false); t1.setPostDate(notOverdue); schedule1.setTransaction(t1); schedule1.setLastPayment(notOverdue); MyMoneyTransaction t2; MyMoneySplit s3, s4; s3.setAccountId("A000001"); t2.addSplit(s3); s4.setAccountId("A000003"); t2.addSplit(s4); MyMoneySchedule schedule2("Schedule 2", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::DirectDeposit, QDate(), QDate(), false, false); t2.setPostDate(notOverdue.addDays(1)); schedule2.setTransaction(t2); schedule2.setLastPayment(notOverdue.addDays(1)); MyMoneyTransaction t3; MyMoneySplit s5, s6; s5.setAccountId("A000005"); t3.addSplit(s5); s6.setAccountId("A000006"); t3.addSplit(s6); MyMoneySchedule schedule3("Schedule 3", Schedule::Type::Transfer, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::Other, QDate(), QDate(), false, false); t3.setPostDate(notOverdue.addDays(2)); schedule3.setTransaction(t3); schedule3.setLastPayment(notOverdue.addDays(2)); MyMoneyTransaction t4; MyMoneySplit s7, s8; s7.setAccountId("A000005"); t4.addSplit(s7); s8.setAccountId("A000006"); t4.addSplit(s8); MyMoneySchedule schedule4("Schedule 4", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::WriteChecque, QDate(), notOverdue.addDays(31), false, false); t4.setPostDate(overdue.addDays(-7)); schedule4.setTransaction(t4); try { + MyMoneyFileTransaction ft; m->addSchedule(schedule1); m->addSchedule(schedule2); m->addSchedule(schedule3); m->addSchedule(schedule4); + ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } QList list; // no filter list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QVERIFY(list.count() == 4); // filter by type list = m->scheduleList(QString(), Schedule::Type::Bill, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QVERIFY(list.count() == 2); QVERIFY(list[0].name() == "Schedule 1"); QVERIFY(list[1].name() == "Schedule 4"); // filter by occurrence list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Daily, Schedule::PaymentType::Any, QDate(), QDate(), false); QVERIFY(list.count() == 1); QVERIFY(list[0].name() == "Schedule 2"); // filter by payment type list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::DirectDeposit, QDate(), QDate(), false); QVERIFY(list.count() == 1); QVERIFY(list[0].name() == "Schedule 2"); // filter by account list = m->scheduleList("A01", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QVERIFY(list.count() == 0); list = m->scheduleList("A000001", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QVERIFY(list.count() == 2); list = m->scheduleList("A000002", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QVERIFY(list.count() == 1); // filter by start date list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(31), QDate(), false); QVERIFY(list.count() == 3); QVERIFY(list[0].name() == "Schedule 2"); QVERIFY(list[1].name() == "Schedule 3"); QVERIFY(list[2].name() == "Schedule 4"); // filter by end date list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), notOverdue.addDays(1), false); QVERIFY(list.count() == 3); QVERIFY(list[0].name() == "Schedule 1"); QVERIFY(list[1].name() == "Schedule 2"); QVERIFY(list[2].name() == "Schedule 4"); // filter by start and end date list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(-1), notOverdue.addDays(1), false); QVERIFY(list.count() == 2); QVERIFY(list[0].name() == "Schedule 1"); QVERIFY(list[1].name() == "Schedule 2"); // filter by overdue status list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), true); QVERIFY(list.count() == 1); QVERIFY(list[0].name() == "Schedule 4"); } -void MyMoneyDatabaseMgrTest::testAddCurrency() +void MyMoneyStorageMgrTest::testAddCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); QVERIFY(m->currencyList().count() == 0); m->setDirty(); try { + MyMoneyFileTransaction ft; m->addCurrency(curr); + ft.commit(); QVERIFY(m->currencyList().count() == 1); QVERIFY((*(m->currencyList().begin())).name() == "Euro"); QVERIFY((*(m->currencyList().begin())).id() == "EUR"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); try { + MyMoneyFileTransaction ft; m->addCurrency(curr); + ft.commit(); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); } } -void MyMoneyDatabaseMgrTest::testModifyCurrency() +void MyMoneyStorageMgrTest::testModifyCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->setDirty(); curr.setName("EURO"); try { + MyMoneyFileTransaction ft; m->modifyCurrency(curr); + ft.commit(); QVERIFY(m->currencyList().count() == 1); QVERIFY((*(m->currencyList().begin())).name() == "EURO"); QVERIFY((*(m->currencyList().begin())).id() == "EUR"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->modifyCurrency(unknownCurr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); } } -void MyMoneyDatabaseMgrTest::testRemoveCurrency() +void MyMoneyStorageMgrTest::testRemoveCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->setDirty(); try { + MyMoneyFileTransaction ft; m->removeCurrency(curr); + ft.commit(); QVERIFY(m->currencyList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { + MyMoneyFileTransaction ft; m->removeCurrency(unknownCurr); + ft.commit(); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); } } -void MyMoneyDatabaseMgrTest::testCurrency() +void MyMoneyStorageMgrTest::testCurrency() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); MyMoneySecurity newCurr; testAddCurrency(); m->setDirty(); try { newCurr = m->currency("EUR"); - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); QVERIFY(newCurr.id() == curr.id()); QVERIFY(newCurr.name() == curr.name()); } catch (const MyMoneyException &e) { unexpectedException(e); } try { m->currency("DEM"); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); } } -void MyMoneyDatabaseMgrTest::testCurrencyList() +void MyMoneyStorageMgrTest::testCurrencyList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QVERIFY(m->currencyList().count() == 0); testAddCurrency(); QVERIFY(m->currencyList().count() == 1); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { + MyMoneyFileTransaction ft; m->addCurrency(unknownCurr); + ft.commit(); m->setDirty(); QVERIFY(m->currencyList().count() == 2); QVERIFY(m->currencyList().count() == 2); - QVERIFY(m->dirty() == false); + QVERIFY(m->dirty() == true); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneyDatabaseMgrTest::testAccountList() +void MyMoneyStorageMgrTest::testAccountList() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); QList accounts; m->accountList(accounts); QVERIFY(accounts.count() == 0); testAddNewAccount(); accounts.clear(); m->accountList(accounts); QVERIFY(accounts.count() == 2); MyMoneyAccount a = m->account("A000001"); MyMoneyAccount b = m->account("A000002"); + MyMoneyFileTransaction ft; m->reparentAccount(b, a); + ft.commit(); accounts.clear(); m->accountList(accounts); QVERIFY(accounts.count() == 2); } -void MyMoneyDatabaseMgrTest::testAddOnlineJob() +void MyMoneyStorageMgrTest::testAddOnlineJob() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); // Add a onlineJob onlineJob job(new dummyTask()); QCOMPARE(m->onlineJobList().count(), 0); m->setDirty(); QSKIP("Test not fully implemented, yet.", SkipAll); try { m->addOnlineJob(job); QCOMPARE(m->onlineJobList().count(), 1); QCOMPARE((*(m->onlineJobList().begin())).id(), QLatin1String("O00000001")); } catch (const MyMoneyException &e) { unexpectedException(e); } // Try to re-add the same job. It should fail. m->setDirty(); try { + MyMoneyFileTransaction ft; m->addOnlineJob(job); + ft.commit(); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QCOMPARE(m->dirty(), false); } } -void MyMoneyDatabaseMgrTest::testModifyOnlineJob() +void MyMoneyStorageMgrTest::testModifyOnlineJob() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); onlineJob job(new dummyTask()); testAddOnlineJob(); m->setDirty(); QSKIP("Test not fully implemented, yet.", SkipAll); // update online job try { + MyMoneyFileTransaction ft; m->modifyOnlineJob(job); + ft.commit(); QVERIFY(m->onlineJobList().count() == 1); //QVERIFY((*(m->onlineJobList().begin())).name() == "EURO"); QVERIFY((*(m->onlineJobList().begin())).id() == "O00000001"); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); onlineJob unknownJob(new dummyTask()); try { + MyMoneyFileTransaction ft; m->modifyOnlineJob(unknownJob); + ft.commit(); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } -void MyMoneyDatabaseMgrTest::testRemoveOnlineJob() +void MyMoneyStorageMgrTest::testRemoveOnlineJob() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); onlineJob job(new dummyTask()); testAddOnlineJob(); m->setDirty(); QSKIP("Test not fully implemented, yet.", SkipAll); try { + MyMoneyFileTransaction ft; m->removeOnlineJob(job); + ft.commit(); QVERIFY(m->onlineJobList().count() == 0); } catch (const MyMoneyException &e) { unexpectedException(e); } m->setDirty(); onlineJob unknownJob(new dummyTask()); try { + MyMoneyFileTransaction ft; m->removeOnlineJob(unknownJob); + ft.commit(); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { QVERIFY(m->dirty() == false); } } -void MyMoneyDatabaseMgrTest::testHighestNumberFromIdString() +void MyMoneyStorageMgrTest::testHighestNumberFromIdString() { testAttachDb(); if (!m_canOpen) QSKIP("Database test skipped because no database could be opened.", SkipAll); testAddTransactions(); - QCOMPARE(m->d_func()->m_sql->d_func()->highestNumberFromIdString(QLatin1String("kmmTransactions"), QLatin1String("id"), 1), 2ul); - QCOMPARE(m->d_func()->m_sql->d_func()->highestNumberFromIdString(QLatin1String("kmmAccounts"), QLatin1String("id"), 1), 6ul); + // disabled since unification of storages +// QCOMPARE(m->d_func()->m_sql->d_func()->highestNumberFromIdString(QLatin1String("kmmTransactions"), QLatin1String("id"), 1), 2ul); +// QCOMPARE(m->d_func()->m_sql->d_func()->highestNumberFromIdString(QLatin1String("kmmAccounts"), QLatin1String("id"), 1), 6ul); } diff --git a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.h b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.h index f329acbf7..6ea8f2334 100644 --- a/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.h +++ b/kmymoney/mymoney/storage/tests/mymoneydatabasemgr-test.h @@ -1,117 +1,122 @@ /*************************************************************************** - mymoneydatabasemgrtest.h + mymoneygenericstoragetest.h ------------------- copyright : (C) 2008 by Fernando Vilas email : fvilas@iname.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYDATABASEMGRTEST_H #define MYMONEYDATABASEMGRTEST_H +#include + #include #include #include #include -#define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyDatabaseMgrTest; +#define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyStorageMgrTest; #include "../mymoneyobject.h" -#include "mymoneydatabasemgr.h" +#include "mymoneystoragemgr.h" +#include "mymoneystoragesql.h" -class MyMoneyDatabaseMgrTest : public QObject +class MyMoneyStorageMgrTest : public QObject { Q_OBJECT protected: - MyMoneyDatabaseMgr *m; + MyMoneyStorageMgr *m; bool m_dbAttached; bool m_canOpen; bool m_haveEmptyDataBase; QUrl m_url; QTemporaryFile m_file; QTemporaryFile m_emptyFile; + std::unique_ptr m_sql; private: QElapsedTimer testStepTimer; QElapsedTimer testCaseTimer; public: - MyMoneyDatabaseMgrTest(); + MyMoneyStorageMgrTest(); + ~MyMoneyStorageMgrTest(); private: void setupUrl(const QString& fname); - void testEquality(const MyMoneyDatabaseMgr* t); + void testEquality(const MyMoneyStorageMgr* t); void copyDatabaseFile(QFile& src, QFile& dest); private Q_SLOTS: void init(); void cleanup(); void testEmptyConstructor(); void testBadConnections(); void testCreateDb(); void testAttachDb(); void testDisconnection(); void testSetFunctions(); void testIsStandardAccount(); void testNewAccount(); void testAccount(); void testAddNewAccount(); void testAddInstitution(); void testInstitution(); void testAccount2Institution(); void testModifyAccount(); void testModifyInstitution(); void testReparentAccount(); void testAddTransactions(); void testTransactionCount(); void testAddBudget(); void testCopyBudget(); void testModifyBudget(); void testRemoveBudget(); void testBalance(); void testModifyTransaction(); void testRemoveUnusedAccount(); void testRemoveUsedAccount(); void testRemoveInstitution(); void testRemoveTransaction(); void testTransactionList(); void testAddPayee(); void testSetAccountName(); void testModifyPayee(); void testPayeeName(); void testRemovePayee(); void testAddTag(); void testModifyTag(); void testTagName(); void testRemoveTag(); void testRemoveAccountFromTree(); // void testAssignment(); // void testDuplicate(); void testAddSchedule(); void testSchedule(); void testModifySchedule(); void testRemoveSchedule(); void testSupportFunctions(); void testScheduleList(); void testAddCurrency(); void testModifyCurrency(); void testRemoveCurrency(); void testCurrency(); void testCurrencyList(); void testAccountList(); void testAddOnlineJob(); void testModifyOnlineJob(); void testRemoveOnlineJob(); void testHighestNumberFromIdString(); }; #endif diff --git a/kmymoney/mymoney/storage/tests/mymoneymap-test.h b/kmymoney/mymoney/storage/tests/mymoneymap-test.h index 43cd51f0d..d2aa7d0de 100644 --- a/kmymoney/mymoney/storage/tests/mymoneymap-test.h +++ b/kmymoney/mymoney/storage/tests/mymoneymap-test.h @@ -1,41 +1,41 @@ /*************************************************************************** mymoneymaptest.h ------------------- copyright : (C) 2007 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYMAPTEST_H #define MYMONEYMAPTEST_H #include #include "mymoneytestutils.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" #include "mymoneymap.h" class MyMoneyMapTest : public QObject { Q_OBJECT protected: MyMoneyMap *m; private Q_SLOTS: void init(); void cleanup(); void testArrayOperator(); void testModifyKey(); void testModifyKeyTwice(); }; #endif diff --git a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp b/kmymoney/mymoney/storage/tests/mymoneystoragemgr-test.cpp similarity index 89% rename from kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp rename to kmymoney/mymoney/storage/tests/mymoneystoragemgr-test.cpp index 6bc910b73..a21f36c2d 100644 --- a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.cpp +++ b/kmymoney/mymoney/storage/tests/mymoneystoragemgr-test.cpp @@ -1,1839 +1,1855 @@ /*************************************************************************** - mymoneyseqaccessmgrtest.cpp + mymoneystoragemgrtest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ -#include "mymoneyseqaccessmgr-test.h" +#include "mymoneystoragemgr-test.h" #include #include #include -#include "mymoneyseqaccessmgr_p.h" +#include "mymoneystoragemgr_p.h" #include "mymoneytestutils.h" #include "mymoneymoney.h" #include "mymoneyfile.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneytag.h" #include "mymoneypayee.h" #include "mymoneyschedule.h" #include "mymoneyschedule_p.h" #include "mymoneyreport.h" #include "mymoneysplit.h" #include "mymoneysplit_p.h" #include "mymoneytransaction.h" #include "mymoneybudget.h" #include "mymoneyprice.h" #include "onlinejob.h" #include "onlinetasks/dummy/tasks/dummytask.h" #include "mymoneyenums.h" #include "mymoneystoragenames.h" using namespace eMyMoney; using namespace MyMoneyStandardAccounts; -QTEST_GUILESS_MAIN(MyMoneySeqAccessMgrTest) +QTEST_GUILESS_MAIN(MyMoneyStorageMgrTest) -void MyMoneySeqAccessMgrTest::init() +void MyMoneyStorageMgrTest::init() { - m = new MyMoneySeqAccessMgr; + m = new MyMoneyStorageMgr; MyMoneyFile* file = MyMoneyFile::instance(); file->attachStorage(m); m->startTransaction(); } -void MyMoneySeqAccessMgrTest::cleanup() +void MyMoneyStorageMgrTest::cleanup() { m->commitTransaction(); MyMoneyFile* file = MyMoneyFile::instance(); file->detachStorage(m); delete m; } -void MyMoneySeqAccessMgrTest::testEmptyConstructor() +void MyMoneyStorageMgrTest::testEmptyConstructor() { MyMoneyPayee user = m->user(); QVERIFY(user.name().isEmpty()); QVERIFY(user.address().isEmpty()); QVERIFY(user.city().isEmpty()); QVERIFY(user.state().isEmpty()); QVERIFY(user.postcode().isEmpty()); QVERIFY(user.telephone().isEmpty()); QVERIFY(user.email().isEmpty()); - QCOMPARE(m->institutionId(), 0ul); - QCOMPARE(m->accountId(), 0ul); - QCOMPARE(m->transactionId(), 0ul); - QCOMPARE(m->payeeId(), 0ul); - QCOMPARE(m->scheduleId(), 0ul); - QCOMPARE(m->reportId(), 0ul); + QCOMPARE(m->d_func()->m_nextInstitutionID, 0ul); + QCOMPARE(m->d_func()->m_nextAccountID, 0ul); + QCOMPARE(m->d_func()->m_nextTransactionID, 0ul); + QCOMPARE(m->d_func()->m_nextPayeeID, 0ul); + QCOMPARE(m->d_func()->m_nextScheduleID, 0ul); + QCOMPARE(m->d_func()->m_nextReportID, 0ul); QCOMPARE(m->d_func()->m_institutionList.count(), 0); QCOMPARE(m->d_func()->m_accountList.count(), 5); QCOMPARE(m->d_func()->m_transactionList.count(), 0); QCOMPARE(m->d_func()->m_transactionKeys.count(), 0); QCOMPARE(m->d_func()->m_payeeList.count(), 0); QCOMPARE(m->d_func()->m_tagList.count(), 0); QCOMPARE(m->d_func()->m_scheduleList.count(), 0); QCOMPARE(m->d_func()->m_dirty, false); QCOMPARE(m->creationDate(), QDate::currentDate()); QCOMPARE(m->liability().name(), QLatin1String("Liability")); QCOMPARE(m->asset().name(), QLatin1String("Asset")); QCOMPARE(m->expense().name(), QLatin1String("Expense")); QCOMPARE(m->income().name(), QLatin1String("Income")); QCOMPARE(m->equity().name(), QLatin1String("Equity")); } -void MyMoneySeqAccessMgrTest::testSetFunctions() +void MyMoneyStorageMgrTest::testSetFunctions() { MyMoneyPayee user = m->user(); m->d_func()->m_dirty = false; user.setName("Name"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->d_func()->m_dirty = false; user.setAddress("Street"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->d_func()->m_dirty = false; user.setCity("Town"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->d_func()->m_dirty = false; user.setState("County"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->d_func()->m_dirty = false; user.setPostcode("Postcode"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->d_func()->m_dirty = false; user.setTelephone("Telephone"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->d_func()->m_dirty = false; user.setEmail("Email"); m->setUser(user); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); m->d_func()->m_dirty = false; m->setValue("key", "value"); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); user = m->user(); QCOMPARE(user.name(), QLatin1String("Name")); QCOMPARE(user.address(), QLatin1String("Street")); QCOMPARE(user.city(), QLatin1String("Town")); QCOMPARE(user.state(), QLatin1String("County")); QCOMPARE(user.postcode(), QLatin1String("Postcode")); QCOMPARE(user.telephone(), QLatin1String("Telephone")); QCOMPARE(user.email(), QLatin1String("Email")); QCOMPARE(m->value("key"), QLatin1String("value")); m->d_func()->m_dirty = false; m->deletePair("key"); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); } -void MyMoneySeqAccessMgrTest::testSupportFunctions() +void MyMoneyStorageMgrTest::testSupportFunctions() { - QCOMPARE(m->nextInstitutionID(), QLatin1String("I000001")); - QCOMPARE(m->institutionId(), 1ul); - QCOMPARE(m->nextAccountID(), QLatin1String("A000001")); - QCOMPARE(m->accountId(), 1ul); - QCOMPARE(m->nextTransactionID(), QLatin1String("T000000000000000001")); - QCOMPARE(m->transactionId(), 1ul); - QCOMPARE(m->nextPayeeID(), QLatin1String("P000001")); - QCOMPARE(m->payeeId(), 1ul); - QCOMPARE(m->nextTagID(), QLatin1String("G000001")); - QCOMPARE(m->tagId(), 1ul); - QCOMPARE(m->nextScheduleID(), QLatin1String("SCH000001")); - QCOMPARE(m->scheduleId(), 1ul); - QCOMPARE(m->nextReportID(), QLatin1String("R000001")); - QCOMPARE(m->reportId(), 1ul); - QCOMPARE(m->nextOnlineJobID(), QLatin1String("O000001")); - QCOMPARE(m->onlineJobId(), 1ul); + QCOMPARE(m->d_func()->nextInstitutionID(), QLatin1String("I000001")); + QCOMPARE(m->d_func()->m_nextInstitutionID, 1ul); + QCOMPARE(m->d_func()->nextAccountID(), QLatin1String("A000001")); + QCOMPARE(m->d_func()->m_nextAccountID, 1ul); + QCOMPARE(m->d_func()->nextTransactionID(), QLatin1String("T000000000000000001")); + QCOMPARE(m->d_func()->m_nextTransactionID, 1ul); + QCOMPARE(m->d_func()->nextPayeeID(), QLatin1String("P000001")); + QCOMPARE(m->d_func()->m_nextPayeeID, 1ul); + QCOMPARE(m->d_func()->nextTagID(), QLatin1String("G000001")); + QCOMPARE(m->d_func()->m_nextTagID, 1ul); + QCOMPARE(m->d_func()->nextScheduleID(), QLatin1String("SCH000001")); + QCOMPARE(m->d_func()->m_nextScheduleID, 1ul); + QCOMPARE(m->d_func()->nextReportID(), QLatin1String("R000001")); + QCOMPARE(m->d_func()->m_nextReportID, 1ul); + QCOMPARE(m->d_func()->nextOnlineJobID(), QLatin1String("O000001")); + QCOMPARE(m->d_func()->m_nextOnlineJobID, 1ul); } -void MyMoneySeqAccessMgrTest::testIsStandardAccount() +void MyMoneyStorageMgrTest::testIsStandardAccount() { QCOMPARE(m->isStandardAccount(stdAccNames[stdAccLiability]), true); QCOMPARE(m->isStandardAccount(stdAccNames[stdAccAsset]), true); QCOMPARE(m->isStandardAccount(stdAccNames[stdAccExpense]), true); QCOMPARE(m->isStandardAccount(stdAccNames[stdAccIncome]), true); QCOMPARE(m->isStandardAccount("A0002"), false); } -void MyMoneySeqAccessMgrTest::testNewAccount() +void MyMoneyStorageMgrTest::testNewAccount() { MyMoneyAccount a; a.setName("AccountName"); a.setNumber("AccountNumber"); m->addAccount(a); m->commitTransaction(); m->startTransaction(); - QCOMPARE(m->accountId(), 1ul); + QCOMPARE(m->d_func()->m_nextAccountID, 1ul); QCOMPARE(m->dirty(), true); QCOMPARE(m->d_func()->m_accountList.count(), 6); QCOMPARE(m->d_func()->m_accountList["A000001"].name(), QLatin1String("AccountName")); } -void MyMoneySeqAccessMgrTest::testAccount() +void MyMoneyStorageMgrTest::testAccount() { testNewAccount(); m->d_func()->m_dirty = false; MyMoneyAccount a; // make sure that an invalid ID causes an exception try { a = m->account("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); // now make sure, that a real ID works try { a = m->account("A000001"); m->commitTransaction(); m->startTransaction(); QCOMPARE(a.name(), QLatin1String("AccountName")); QCOMPARE(a.id(), QLatin1String("A000001")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testAddNewAccount() +void MyMoneyStorageMgrTest::testAddNewAccount() { testNewAccount(); MyMoneyAccount a, b; b.setName("Account2"); b.setNumber("Acc2"); m->addAccount(b); m->commitTransaction(); m->startTransaction(); m->d_func()->m_dirty = false; - QCOMPARE(m->accountId(), 2ul); + QCOMPARE(m->d_func()->m_nextAccountID, 2ul); QCOMPARE(m->d_func()->m_accountList.count(), 7); // try to add account to undefined account try { MyMoneyAccount c("UnknownID", b); m->addAccount(c, a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); // now try to add account 1 as sub-account to account 2 a = m->account("A000001"); try { QCOMPARE(m->d_func()->m_accountList[stdAccNames[stdAccAsset]].accountList().count(), 0); m->addAccount(b, a); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->d_func()->m_accountList["A000002"].accountList()[0], QLatin1String("A000001")); QCOMPARE(m->d_func()->m_accountList["A000002"].accountList().count(), 1); QCOMPARE(m->d_func()->m_accountList[stdAccNames[stdAccAsset]].accountList().count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testAddInstitution() +void MyMoneyStorageMgrTest::testAddInstitution() { MyMoneyInstitution i; i.setName("Inst Name"); m->addInstitution(i); QCOMPARE(m->d_func()->m_institutionList.count(), 1); - QCOMPARE(m->institutionId(), 1ul); + QCOMPARE(m->d_func()->m_nextInstitutionID, 1ul); QCOMPARE(m->d_func()->m_institutionList["I000001"].name(), QLatin1String("Inst Name")); } -void MyMoneySeqAccessMgrTest::testInstitution() +void MyMoneyStorageMgrTest::testInstitution() { testAddInstitution(); MyMoneyInstitution i; m->d_func()->m_dirty = false; // try to find unknown institution try { i = m->institution("Unknown ID"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QCOMPARE(m->dirty(), false); // now try to find real institution try { i = m->institution("I000001"); QCOMPARE(i.name(), QLatin1String("Inst Name")); QCOMPARE(m->dirty(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testAccount2Institution() +void MyMoneyStorageMgrTest::testAccount2Institution() { testAddInstitution(); testAddNewAccount(); MyMoneyInstitution i; MyMoneyAccount a, b; try { i = m->institution("I000001"); a = m->account("A000001"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->d_func()->m_dirty = false; // try to add to a false institution MyMoneyInstitution fake("Unknown ID", i); a.setInstitutionId(fake.id()); try { m->modifyAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); // now try to do it with a real institution try { QCOMPARE(i.accountList().count(), 0); a.setInstitutionId(i.id()); m->modifyAccount(a); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(a.institutionId(), i.id()); b = m->account("A000001"); QCOMPARE(b.institutionId(), i.id()); QCOMPARE(i.accountList().count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testModifyAccount() +void MyMoneyStorageMgrTest::testModifyAccount() { testAccount2Institution(); // test the OK case MyMoneyAccount a = m->account("A000001"); a.setName("New account name"); m->d_func()->m_dirty = false; try { m->modifyAccount(a); m->commitTransaction(); m->startTransaction(); MyMoneyAccount b = m->account("A000001"); QCOMPARE(b.parentAccountId(), a.parentAccountId()); QCOMPARE(b.name(), QLatin1String("New account name")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // modify institution to unknown id MyMoneyAccount c("Unknown ID", a); m->d_func()->m_dirty = false; try { m->modifyAccount(c); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different account type MyMoneyAccount d; d.setAccountType(Account::Type::CreditCard); MyMoneyAccount f("A000001", d); try { m->modifyAccount(f); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // use different parent a.setParentAccountId("A000002"); try { m->modifyAccount(c); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneySeqAccessMgrTest::testModifyInstitution() +void MyMoneyStorageMgrTest::testModifyInstitution() { testAddInstitution(); MyMoneyInstitution i = m->institution("I000001"); m->d_func()->m_dirty = false; i.setName("New inst name"); try { m->modifyInstitution(i); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); i = m->institution("I000001"); QCOMPARE(i.name(), QLatin1String("New inst name")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // try to modify an institution that does not exist MyMoneyInstitution f("Unknown ID", i); try { m->modifyInstitution(f); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneySeqAccessMgrTest::testReparentAccount() +void MyMoneyStorageMgrTest::testReparentAccount() { // this one adds some accounts to the database MyMoneyAccount ex1; ex1.setAccountType(Account::Type::Expense); MyMoneyAccount ex2; ex2.setAccountType(Account::Type::Expense); MyMoneyAccount ex3; ex3.setAccountType(Account::Type::Expense); MyMoneyAccount ex4; ex4.setAccountType(Account::Type::Expense); MyMoneyAccount in; in.setAccountType(Account::Type::Income); MyMoneyAccount ch; ch.setAccountType(Account::Type::Checkings); ex1.setName("Sales Tax"); ex2.setName("Sales Tax 16%"); ex3.setName("Sales Tax 7%"); ex4.setName("Grosseries"); in.setName("Salary"); ch.setName("My checkings account"); try { m->addAccount(ex1); m->addAccount(ex2); m->addAccount(ex3); m->addAccount(ex4); m->addAccount(in); m->addAccount(ch); QCOMPARE(ex1.id(), QLatin1String("A000001")); QCOMPARE(ex2.id(), QLatin1String("A000002")); QCOMPARE(ex3.id(), QLatin1String("A000003")); QCOMPARE(ex4.id(), QLatin1String("A000004")); QCOMPARE(in.id(), QLatin1String("A000005")); QCOMPARE(ch.id(), QLatin1String("A000006")); MyMoneyAccount parent = m->expense(); m->addAccount(parent, ex1); m->addAccount(ex1, ex2); m->addAccount(parent, ex3); m->addAccount(parent, ex4); parent = m->income(); m->addAccount(parent, in); parent = m->asset(); m->addAccount(parent, ch); QCOMPARE(m->expense().accountCount(), 3); QCOMPARE(m->account(ex1.id()).accountCount(), 1); QCOMPARE(ex3.parentAccountId(), stdAccNames[stdAccExpense]); m->reparentAccount(ex3, ex1); QCOMPARE(m->expense().accountCount(), 2); QCOMPARE(m->account(ex1.id()).accountCount(), 2); QCOMPARE(ex3.parentAccountId(), ex1.id()); } catch (const MyMoneyException &e) { std::cout << std::endl << qPrintable(e.what()) << std::endl; QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testAddTransactions() +void MyMoneyStorageMgrTest::testAddTransactions() { testReparentAccount(); MyMoneyAccount ch; MyMoneyTransaction t1, t2; MyMoneySplit s; try { // I made some money, great s.setAccountId("A000006"); // Checkings s.setShares(MyMoneyMoney(100000, 100)); s.setValue(MyMoneyMoney(100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000005"); // Salary s.setShares(MyMoneyMoney(-100000, 100)); s.setValue(MyMoneyMoney(-100000, 100)); QVERIFY(s.id().isEmpty()); t1.addSplit(s); t1.setPostDate(QDate(2002, 5, 10)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->d_func()->m_dirty = false; try { m->addTransaction(t1); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(t1.id(), QLatin1String("T000000000000000001")); QCOMPARE(t1.splitCount(), 2u); QCOMPARE(m->transactionCount(QString()), 1u); } catch (const MyMoneyException &e) { unexpectedException(e); } try { // I spent some money, not so great s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000004"); // Grosseries s.setShares(MyMoneyMoney(10000, 100)); s.setValue(MyMoneyMoney(10000, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000002"); // 16% sales tax s.setShares(MyMoneyMoney(1200, 100)); s.setValue(MyMoneyMoney(1200, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000003"); // 7% sales tax s.setShares(MyMoneyMoney(400, 100)); s.setValue(MyMoneyMoney(400, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); s.d_func()->setId(QString()); // enable re-usage of split variable s.setAccountId("A000006"); // Checkings account s.setShares(MyMoneyMoney(-11600, 100)); s.setValue(MyMoneyMoney(-11600, 100)); QVERIFY(s.id().isEmpty()); t2.addSplit(s); t2.setPostDate(QDate(2002, 5, 9)); } catch (const MyMoneyException &e) { unexpectedException(e); } m->d_func()->m_dirty = false; try { m->addTransaction(t2); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(t2.id(), QLatin1String("T000000000000000002")); QCOMPARE(t2.splitCount(), 4u); QCOMPARE(m->transactionCount(QString()), 2u); QMap::ConstIterator it_k; QMap::ConstIterator it_t; it_k = m->d_func()->m_transactionKeys.begin(); it_t = m->d_func()->m_transactionList.begin(); QCOMPARE((*it_k), QLatin1String("2002-05-10-T000000000000000001")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000002")); ++it_k; ++it_t; QCOMPARE((*it_k), QLatin1String("2002-05-09-T000000000000000002")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000001")); ++it_k; ++it_t; QCOMPARE(it_k, m->d_func()->m_transactionKeys.end()); QCOMPARE(it_t, m->d_func()->m_transactionList.end()); ch = m->account("A000006"); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.size(), 2); QList::ConstIterator it; it = list.constBegin(); QCOMPARE((*it).id(), QLatin1String("T000000000000000002")); ++it; QCOMPARE((*it).id(), QLatin1String("T000000000000000001")); ++it; QCOMPARE(it, list.constEnd()); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneySeqAccessMgrTest::testTransactionCount() +void MyMoneyStorageMgrTest::testTransactionCount() { testAddTransactions(); QCOMPARE(m->transactionCount("A000001"), 0u); QCOMPARE(m->transactionCount("A000002"), 1u); QCOMPARE(m->transactionCount("A000003"), 1u); QCOMPARE(m->transactionCount("A000004"), 1u); QCOMPARE(m->transactionCount("A000005"), 1u); QCOMPARE(m->transactionCount("A000006"), 2u); } -void MyMoneySeqAccessMgrTest::testBalance() +void MyMoneyStorageMgrTest::testBalance() { testAddTransactions(); QVERIFY(m->balance("A000001", QDate()).isZero()); QCOMPARE(m->balance("A000002", QDate()), MyMoneyMoney(1200, 100)); QCOMPARE(m->balance("A000003", QDate()), MyMoneyMoney(400, 100)); QCOMPARE(m->totalBalance("A000001", QDate()), MyMoneyMoney(1600, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 9)), MyMoneyMoney(-11600, 100)); QCOMPARE(m->balance("A000005", QDate(2002, 5, 10)), MyMoneyMoney(-100000, 100)); QCOMPARE(m->balance("A000006", QDate(2002, 5, 10)), MyMoneyMoney(88400, 100)); } -void MyMoneySeqAccessMgrTest::testModifyTransaction() +void MyMoneyStorageMgrTest::testModifyTransaction() { testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); MyMoneySplit s; MyMoneyAccount ch; // just modify simple stuff (splits) QCOMPARE(t.splitCount(), 4u); s = t.splits()[0]; s.setShares(MyMoneyMoney(11000, 100)); s.setValue(MyMoneyMoney(11000, 100)); t.modifySplit(s); QCOMPARE(t.splitCount(), 4u); s = t.splits()[3]; s.setShares(MyMoneyMoney(-12600, 100)); s.setValue(MyMoneyMoney(-12600, 100)); t.modifySplit(s); try { QCOMPARE(m->balance("A000004", QDate()), MyMoneyMoney(10000, 100)); QCOMPARE(m->balance("A000006", QDate()), MyMoneyMoney(100000 - 11600, 100)); QCOMPARE(m->totalBalance("A000001", QDate()), MyMoneyMoney(1600, 100)); m->modifyTransaction(t); QCOMPARE(m->balance("A000004", QDate()), MyMoneyMoney(11000, 100)); QCOMPARE(m->balance("A000006", QDate()), MyMoneyMoney(100000 - 12600, 100)); QCOMPARE(m->totalBalance("A000001", QDate()), MyMoneyMoney(1600, 100)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // now modify the date t.setPostDate(QDate(2002, 5, 11)); try { m->modifyTransaction(t); QCOMPARE(m->balance("A000004", QDate()), MyMoneyMoney(11000, 100)); QCOMPARE(m->balance("A000006", QDate()), MyMoneyMoney(100000 - 12600, 100)); QCOMPARE(m->totalBalance("A000001", QDate()), MyMoneyMoney(1600, 100)); QMap::ConstIterator it_k; QMap::ConstIterator it_t; it_k = m->d_func()->m_transactionKeys.begin(); it_t = m->d_func()->m_transactionList.begin(); QCOMPARE((*it_k), QLatin1String("2002-05-10-T000000000000000001")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000001")); ++it_k; ++it_t; QCOMPARE((*it_k), QLatin1String("2002-05-11-T000000000000000002")); QCOMPARE((*it_t).id(), QLatin1String("T000000000000000002")); ++it_k; ++it_t; QCOMPARE(it_k, m->d_func()->m_transactionKeys.end()); QCOMPARE(it_t, m->d_func()->m_transactionList.end()); ch = m->account("A000006"); // check that the account's transaction list is updated QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.size(), 2); QList::ConstIterator it; it = list.constBegin(); QCOMPARE((*it).id(), QLatin1String("T000000000000000001")); ++it; QCOMPARE((*it).id(), QLatin1String("T000000000000000002")); ++it; QCOMPARE(it, list.constEnd()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testRemoveUnusedAccount() +void MyMoneyStorageMgrTest::testRemoveUnusedAccount() { testAccount2Institution(); MyMoneyAccount a = m->account("A000001"); MyMoneyInstitution i = m->institution("I000001"); m->d_func()->m_dirty = false; // make sure, we cannot remove the standard account groups try { m->removeAccount(m->liability()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->asset()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->expense()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { m->removeAccount(m->income()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // try to remove the account still attached to the institution try { m->removeAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } // now really remove an account try { QCOMPARE(i.accountCount(), 0u); QCOMPARE(m->accountCount(), 7u); a.setInstitutionId(QString()); m->modifyAccount(a); m->removeAccount(a); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->accountCount(), 6u); QCOMPARE(m->dirty(), true); i = m->institution("I000001"); QCOMPARE(i.accountCount(), 0u); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testRemoveUsedAccount() +void MyMoneyStorageMgrTest::testRemoveUsedAccount() { testAddTransactions(); MyMoneyAccount a = m->account("A000006"); try { m->removeAccount(a); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneySeqAccessMgrTest::testRemoveInstitution() +void MyMoneyStorageMgrTest::testRemoveInstitution() { testModifyInstitution(); testReparentAccount(); MyMoneyInstitution i; MyMoneyAccount a; // assign the checkings account to the institution try { i = m->institution("I000001"); a = m->account("A000006"); a.setInstitutionId(i.id()); m->modifyAccount(a); QCOMPARE(i.accountCount(), 0u); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->d_func()->m_dirty = false; // now remove the institution and see if the account survived ;-) try { m->removeInstitution(i); a.setInstitutionId(QString()); m->modifyAccount(a); m->commitTransaction(); m->startTransaction(); a = m->account("A000006"); QCOMPARE(m->dirty(), true); QVERIFY(a.institutionId().isEmpty()); QCOMPARE(m->institutionCount(), 0u); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testRemoveTransaction() +void MyMoneyStorageMgrTest::testRemoveTransaction() { testAddTransactions(); MyMoneyTransaction t = m->transaction("T000000000000000002"); m->d_func()->m_dirty = false; try { m->removeTransaction(t); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); QCOMPARE(m->transactionCount(QString()), 1u); /* removed with MyMoneyAccount::Transaction QCOMPARE(m->account("A000006").transactionCount(QString()), 1); */ } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testTransactionList() +void MyMoneyStorageMgrTest::testTransactionList() { testAddTransactions(); QList list; MyMoneyTransactionFilter filter("A000006"); list = m->transactionList(filter); QCOMPARE(list.count(), 2); QCOMPARE(list.at(0).id(), QLatin1String("T000000000000000002")); QCOMPARE(list.at(1).id(), QLatin1String("T000000000000000001")); filter.clear(); filter.addAccount(QString("A000003")); list = m->transactionList(filter); QCOMPARE(list.count(), 1); QCOMPARE(list.at(0).id(), QLatin1String("T000000000000000002")); filter.clear(); list = m->transactionList(filter); QCOMPARE(list.count(), 2); QCOMPARE(list.at(0).id(), QLatin1String("T000000000000000002")); QCOMPARE(list.at(1).id(), QLatin1String("T000000000000000001")); } -void MyMoneySeqAccessMgrTest::testAddPayee() +void MyMoneyStorageMgrTest::testAddPayee() { MyMoneyPayee p; p.setName("THB"); m->d_func()->m_dirty = false; try { - QCOMPARE(m->payeeId(), 0ul); + QCOMPARE(m->d_func()->m_nextPayeeID, 0ul); m->addPayee(p); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); - QCOMPARE(m->payeeId(), 1ul); + QCOMPARE(m->d_func()->m_nextPayeeID, 1ul); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testSetAccountName() +void MyMoneyStorageMgrTest::testSetAccountName() { try { m->setAccountName(stdAccNames[stdAccLiability], "Verbindlichkeiten"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->setAccountName(stdAccNames[stdAccAsset], QString("Vermögen")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->setAccountName(stdAccNames[stdAccExpense], "Ausgaben"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->setAccountName(stdAccNames[stdAccIncome], "Einnahmen"); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } QCOMPARE(m->liability().name(), QLatin1String("Verbindlichkeiten")); QCOMPARE(m->asset().name(), QString("Vermögen")); QCOMPARE(m->expense().name(), QLatin1String("Ausgaben")); QCOMPARE(m->income().name(), QLatin1String("Einnahmen")); try { m->setAccountName("A000001", "New account name"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneySeqAccessMgrTest::testModifyPayee() +void MyMoneyStorageMgrTest::testModifyPayee() { MyMoneyPayee p; testAddPayee(); p = m->payee("P000001"); p.setName("New name"); m->d_func()->m_dirty = false; try { m->modifyPayee(p); m->commitTransaction(); m->startTransaction(); p = m->payee("P000001"); QCOMPARE(p.name(), QLatin1String("New name")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testRemovePayee() +void MyMoneyStorageMgrTest::testRemovePayee() { testAddPayee(); m->d_func()->m_dirty = false; // check that we can remove an unreferenced payee MyMoneyPayee p = m->payee("P000001"); try { QCOMPARE(m->d_func()->m_payeeList.count(), 1); m->removePayee(p); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->d_func()->m_payeeList.count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; sp.setPayeeId("P000001"); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown payee try { m->modifyTransaction(tr); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } m->d_func()->m_nextPayeeID = 0; // reset here, so that the // testAddPayee will not fail testAddPayee(); // check that it works when the payee exists try { m->modifyTransaction(tr); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->d_func()->m_dirty = false; // now check, that we cannot remove the payee try { m->removePayee(p); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QCOMPARE(m->d_func()->m_payeeList.count(), 1); } -void MyMoneySeqAccessMgrTest::testAddTag() +void MyMoneyStorageMgrTest::testAddTag() { MyMoneyTag ta; ta.setName("THB"); m->d_func()->m_dirty = false; try { - QCOMPARE(m->tagId(), 0ul); + QCOMPARE(m->d_func()->m_nextTagID, 0ul); m->addTag(ta); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), true); - QCOMPARE(m->tagId(), 1ul); + QCOMPARE(m->d_func()->m_nextTagID, 1ul); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testModifyTag() +void MyMoneyStorageMgrTest::testModifyTag() { MyMoneyTag ta; testAddTag(); ta = m->tag("G000001"); ta.setName("New name"); m->d_func()->m_dirty = false; try { m->modifyTag(ta); m->commitTransaction(); m->startTransaction(); ta = m->tag("G000001"); QCOMPARE(ta.name(), QLatin1String("New name")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testRemoveTag() +void MyMoneyStorageMgrTest::testRemoveTag() { testAddTag(); m->d_func()->m_dirty = false; // check that we can remove an unreferenced tag MyMoneyTag ta = m->tag("G000001"); try { QCOMPARE(m->d_func()->m_tagList.count(), 1); m->removeTag(ta); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->d_func()->m_tagList.count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } // add transaction testAddTransactions(); MyMoneyTransaction tr = m->transaction("T000000000000000001"); MyMoneySplit sp; sp = tr.splits()[0]; QList tagIdList; tagIdList << "G000001"; sp.setTagIdList(tagIdList); tr.modifySplit(sp); // check that we cannot add a transaction referencing // an unknown tag try { m->modifyTransaction(tr); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } m->d_func()->m_nextTagID = 0; // reset here, so that the // testAddTag will not fail testAddTag(); // check that it works when the tag exists try { m->modifyTransaction(tr); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->d_func()->m_dirty = false; // now check, that we cannot remove the tag try { m->removeTag(ta); QFAIL("Expected exception"); } catch (const MyMoneyException &) { } QCOMPARE(m->d_func()->m_tagList.count(), 1); } -void MyMoneySeqAccessMgrTest::testRemoveAccountFromTree() +void MyMoneyStorageMgrTest::testRemoveAccountFromTree() { MyMoneyAccount a, b, c; a.setName("Acc A"); b.setName("Acc B"); c.setName("Acc C"); // build a tree A -> B -> C, remove B and see if A -> C // remains in the storag manager try { m->addAccount(a); m->addAccount(b); m->addAccount(c); m->reparentAccount(b, a); m->reparentAccount(c, b); QCOMPARE(a.accountList().count(), 1); QCOMPARE(m->account(a.accountList()[0]).name(), QLatin1String("Acc B")); QCOMPARE(b.accountList().count(), 1); QCOMPARE(m->account(b.accountList()[0]).name(), QLatin1String("Acc C")); QCOMPARE(c.accountList().count(), 0); m->removeAccount(b); // reload account info from titutionIDtorage a = m->account(a.id()); c = m->account(c.id()); try { b = m->account(b.id()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } QCOMPARE(a.accountList().count(), 1); QCOMPARE(m->account(a.accountList()[0]).name(), QLatin1String("Acc C")); QCOMPARE(c.accountList().count(), 0); } catch (const MyMoneyException &e) { unexpectedException(e); } } -void MyMoneySeqAccessMgrTest::testPayeeName() +void MyMoneyStorageMgrTest::testPayeeName() { testAddPayee(); MyMoneyPayee p; QString name("THB"); // OK case try { p = m->payeeByName(name); QCOMPARE(p.name(), QLatin1String("THB")); QCOMPARE(p.id(), QLatin1String("P000001")); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { p = m->payeeByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneySeqAccessMgrTest::testTagName() +void MyMoneyStorageMgrTest::testTagName() { testAddTag(); MyMoneyTag ta; QString name("THB"); // OK case try { ta = m->tagByName(name); QCOMPARE(ta.name(), QLatin1String("THB")); QCOMPARE(ta.id(), QLatin1String("G000001")); } catch (const MyMoneyException &e) { unexpectedException(e); } // Not OK case name = "Thb"; try { ta = m->tagByName(name); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } // disabled because of no real world use case -//void MyMoneySeqAccessMgrTest::testAssignment() +//void MyMoneyStorageMgrTest::testAssignment() //{ // testAddTransactions(); // MyMoneyPayee user; // user.setName("Thomas"); // m->setUser(user); -// MyMoneySeqAccessMgr test = *m; +// MyMoneyStorageMgr test = *m; // testEquality(&test); //} -void MyMoneySeqAccessMgrTest::testEquality(const MyMoneySeqAccessMgr *t) +void MyMoneyStorageMgrTest::testEquality(const MyMoneyStorageMgr *t) { QCOMPARE(m->user().name(), t->user().name()); QCOMPARE(m->user().address(), t->user().address()); QCOMPARE(m->user().city(), t->user().city()); QCOMPARE(m->user().state(), t->user().state()); QCOMPARE(m->user().postcode(), t->user().postcode()); QCOMPARE(m->user().telephone(), t->user().telephone()); QCOMPARE(m->user().email(), t->user().email()); - QCOMPARE(m->institutionId(), t->institutionId()); - QCOMPARE(m->accountId(), t->accountId()); - QCOMPARE(m->transactionId(), t->transactionId()); - QCOMPARE(m->payeeId(), t->payeeId()); - QCOMPARE(m->tagId(), t->tagId()); - QCOMPARE(m->scheduleId(), t->scheduleId()); + QCOMPARE(m->d_func()->m_nextInstitutionID, t->d_func()->m_nextInstitutionID); + QCOMPARE(m->d_func()->m_nextAccountID, t->d_func()->m_nextAccountID); + QCOMPARE(m->d_func()->m_nextTransactionID, t->d_func()->m_nextTransactionID); + QCOMPARE(m->d_func()->m_nextPayeeID, t->d_func()->m_nextPayeeID); + QCOMPARE(m->d_func()->m_nextTagID, t->d_func()->m_nextTagID); + QCOMPARE(m->d_func()->m_nextScheduleID, t->d_func()->m_nextScheduleID); QCOMPARE(m->dirty(), t->dirty()); QCOMPARE(m->creationDate(), t->creationDate()); QCOMPARE(m->lastModificationDate(), t->lastModificationDate()); /* * make sure, that the keys and values are the same * on the left and the right side */ QCOMPARE(m->d_func()->m_payeeList.keys(), t->d_func()->m_payeeList.keys()); QCOMPARE(m->d_func()->m_payeeList.values(), t->d_func()->m_payeeList.values()); QCOMPARE(m->d_func()->m_tagList.keys(), t->d_func()->m_tagList.keys()); QCOMPARE(m->d_func()->m_tagList.values(), t->d_func()->m_tagList.values()); QCOMPARE(m->d_func()->m_transactionKeys.keys(), t->d_func()->m_transactionKeys.keys()); QCOMPARE(m->d_func()->m_transactionKeys.values(), t->d_func()->m_transactionKeys.values()); QCOMPARE(m->d_func()->m_institutionList.keys(), t->d_func()->m_institutionList.keys()); QCOMPARE(m->d_func()->m_institutionList.values(), t->d_func()->m_institutionList.values()); QCOMPARE(m->d_func()->m_accountList.keys(), t->d_func()->m_accountList.keys()); QCOMPARE(m->d_func()->m_accountList.values(), t->d_func()->m_accountList.values()); QCOMPARE(m->d_func()->m_transactionList.keys(), t->d_func()->m_transactionList.keys()); QCOMPARE(m->d_func()->m_transactionList.values(), t->d_func()->m_transactionList.values()); // QCOMPARE(m->d_func()->m_scheduleList.keys(), t->m_scheduleList.keys()); // QCOMPARE(m->d_func()->m_scheduleList.values(), t->m_scheduleList.values()); } // disabled because of no real world use case -//void MyMoneySeqAccessMgrTest::testDuplicate() +//void MyMoneyStorageMgrTest::testDuplicate() //{ -// const MyMoneySeqAccessMgr* t; +// const MyMoneyStorageMgr* t; // testModifyTransaction(); // t = m->duplicate(); // testEquality(t); // delete t; //} -void MyMoneySeqAccessMgrTest::testAddSchedule() +void MyMoneyStorageMgrTest::testAddSchedule() { /* Note addSchedule() now calls validate as it should * so we need an account id. Later this will * be checked to make sure its a valid account id. The * tests currently fail because no splits are defined * for the schedules transaction. */ try { QCOMPARE(m->d_func()->m_scheduleList.count(), 0); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); + MyMoneyAccount a1("A000001", MyMoneyAccount()); + MyMoneyAccount a2("A000002", MyMoneyAccount()); + m->addAccount(a1); + m->addAccount(a2); + MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); t1.setPostDate(QDate(2003, 7, 10)); schedule.setTransaction(t1); - m->addSchedule(schedule); QCOMPARE(m->d_func()->m_scheduleList.count(), 1); QCOMPARE(schedule.id(), QLatin1String("SCH000001")); QCOMPARE(m->d_func()->m_scheduleList["SCH000001"].id(), QLatin1String("SCH000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { MyMoneySchedule schedule("Sched-Name", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::ManualDeposit, QDate(), QDate(), true, false); m->addSchedule(schedule); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneySeqAccessMgrTest::testSchedule() +void MyMoneyStorageMgrTest::testSchedule() { testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); QCOMPARE(sched.name(), QLatin1String("Sched-Name")); QCOMPARE(sched.id(), QLatin1String("SCH000001")); try { m->schedule("SCH000002"); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } -void MyMoneySeqAccessMgrTest::testModifySchedule() +void MyMoneyStorageMgrTest::testModifySchedule() { testAddSchedule(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); sched.d_func()->setId("SCH000002"); try { m->modifySchedule(sched); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } sched = m->schedule("SCH000001"); sched.setName("New Sched-Name"); try { m->modifySchedule(sched); QCOMPARE(m->d_func()->m_scheduleList.count(), 1); QCOMPARE(m->d_func()->m_scheduleList["SCH000001"].name(), QLatin1String("New Sched-Name")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testRemoveSchedule() +void MyMoneyStorageMgrTest::testRemoveSchedule() { testAddSchedule(); m->commitTransaction(); m->startTransaction(); MyMoneySchedule sched; sched = m->schedule("SCH000001"); sched.d_func()->setId("SCH000002"); try { m->removeSchedule(sched); m->commitTransaction(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { m->rollbackTransaction(); } m->startTransaction(); sched = m->schedule("SCH000001"); try { m->removeSchedule(sched); m->commitTransaction(); QCOMPARE(m->d_func()->m_scheduleList.count(), 0); } catch (const MyMoneyException &) { m->rollbackTransaction(); QFAIL("Unexpected exception"); } m->startTransaction(); } -void MyMoneySeqAccessMgrTest::testScheduleList() +void MyMoneyStorageMgrTest::testScheduleList() { QDate testDate = QDate::currentDate(); QDate notOverdue = testDate.addDays(2); QDate overdue = testDate.addDays(-2); MyMoneyTransaction t1; MyMoneySplit s1, s2; s1.setAccountId("A000001"); t1.addSplit(s1); s2.setAccountId("A000002"); t1.addSplit(s2); + MyMoneyAccount a1("A000001", MyMoneyAccount()); + MyMoneyAccount a2("A000002", MyMoneyAccount()); + m->addAccount(a1); + m->addAccount(a2); MyMoneySchedule schedule1("Schedule 1", Schedule::Type::Bill, Schedule::Occurrence::Once, 1, Schedule::PaymentType::DirectDebit, QDate(), QDate(), false, false); t1.setPostDate(notOverdue); schedule1.setTransaction(t1); schedule1.setLastPayment(notOverdue); MyMoneyTransaction t2; MyMoneySplit s3, s4; s3.setAccountId("A000001"); t2.addSplit(s3); s4.setAccountId("A000003"); t2.addSplit(s4); + MyMoneyAccount a3("A000003", MyMoneyAccount()); + m->addAccount(a3); MyMoneySchedule schedule2("Schedule 2", Schedule::Type::Deposit, Schedule::Occurrence::Daily, 1, Schedule::PaymentType::DirectDeposit, QDate(), QDate(), false, false); t2.setPostDate(notOverdue.addDays(1)); schedule2.setTransaction(t2); schedule2.setLastPayment(notOverdue.addDays(1)); MyMoneyTransaction t3; MyMoneySplit s5, s6; s5.setAccountId("A000005"); t3.addSplit(s5); s6.setAccountId("A000006"); t3.addSplit(s6); + MyMoneyAccount a5("A000005", MyMoneyAccount()); + MyMoneyAccount a6("A000006", MyMoneyAccount()); + MyMoneyAccount a7("A000007", MyMoneyAccount()); + m->addAccount(a5); + m->addAccount(a6); + m->addAccount(a7); MyMoneySchedule schedule3("Schedule 3", Schedule::Type::Transfer, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::Other, QDate(), QDate(), false, false); t3.setPostDate(notOverdue.addDays(2)); schedule3.setTransaction(t3); schedule3.setLastPayment(notOverdue.addDays(2)); MyMoneyTransaction t4; MyMoneySplit s7, s8; s7.setAccountId("A000005"); t4.addSplit(s7); s8.setAccountId("A000006"); t4.addSplit(s8); MyMoneySchedule schedule4("Schedule 4", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::WriteChecque, QDate(), notOverdue.addDays(31), false, false); t4.setPostDate(overdue.addDays(-7)); schedule4.setTransaction(t4); try { m->addSchedule(schedule1); m->addSchedule(schedule2); m->addSchedule(schedule3); m->addSchedule(schedule4); } catch (const MyMoneyException &e) { qDebug("Error: %s", qPrintable(e.what())); QFAIL("Unexpected exception"); } QList list; // no filter list = m->scheduleList(QString(), Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QCOMPARE(list.count(), 4); // filter by type list = m->scheduleList("", Schedule::Type::Bill, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QCOMPARE(list.count(), 2); QCOMPARE(list[0].name(), QLatin1String("Schedule 1")); QCOMPARE(list[1].name(), QLatin1String("Schedule 4")); // filter by occurrence list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Daily, Schedule::PaymentType::Any, QDate(), QDate(), false); QCOMPARE(list.count(), 1); QCOMPARE(list[0].name(), QLatin1String("Schedule 2")); // filter by payment type list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::DirectDeposit, QDate(), QDate(), false); QCOMPARE(list.count(), 1); QCOMPARE(list[0].name(), QLatin1String("Schedule 2")); // filter by account list = m->scheduleList("A01", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QCOMPARE(list.count(), 0); list = m->scheduleList("A000001", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QCOMPARE(list.count(), 2); list = m->scheduleList("A000002", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), false); QCOMPARE(list.count(), 1); // filter by start date list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(31), QDate(), false); QCOMPARE(list.count(), 3); QCOMPARE(list[0].name(), QLatin1String("Schedule 2")); QCOMPARE(list[1].name(), QLatin1String("Schedule 3")); QCOMPARE(list[2].name(), QLatin1String("Schedule 4")); // filter by end date list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), notOverdue.addDays(1), false); QCOMPARE(list.count(), 3); QCOMPARE(list[0].name(), QLatin1String("Schedule 1")); QCOMPARE(list[1].name(), QLatin1String("Schedule 2")); QCOMPARE(list[2].name(), QLatin1String("Schedule 4")); // filter by start and end date list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, notOverdue.addDays(-1), notOverdue.addDays(1), false); QCOMPARE(list.count(), 2); QCOMPARE(list[0].name(), QLatin1String("Schedule 1")); QCOMPARE(list[1].name(), QLatin1String("Schedule 2")); // filter by overdue status list = m->scheduleList("", Schedule::Type::Any, Schedule::Occurrence::Any, Schedule::PaymentType::Any, QDate(), QDate(), true); QCOMPARE(list.count(), 1); QCOMPARE(list[0].name(), QLatin1String("Schedule 4")); } -void MyMoneySeqAccessMgrTest::testAddCurrency() +void MyMoneyStorageMgrTest::testAddCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); QCOMPARE(m->d_func()->m_currencyList.count(), 0); m->d_func()->m_dirty = false; try { m->addCurrency(curr); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->d_func()->m_currencyList.count(), 1); QCOMPARE(m->d_func()->m_currencyList["EUR"].name(), QLatin1String("Euro")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->d_func()->m_dirty = false; try { m->addCurrency(curr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } -void MyMoneySeqAccessMgrTest::testModifyCurrency() +void MyMoneyStorageMgrTest::testModifyCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->d_func()->m_dirty = false; curr.setName("EURO"); try { m->modifyCurrency(curr); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->d_func()->m_currencyList.count(), 1); QCOMPARE(m->d_func()->m_currencyList["EUR"].name(), QLatin1String("EURO")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->d_func()->m_dirty = false; MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->modifyCurrency(unknownCurr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } -void MyMoneySeqAccessMgrTest::testRemoveCurrency() +void MyMoneyStorageMgrTest::testRemoveCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); testAddCurrency(); m->d_func()->m_dirty = false; try { m->removeCurrency(curr); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->d_func()->m_currencyList.count(), 0); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } m->d_func()->m_dirty = false; MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->removeCurrency(unknownCurr); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } -void MyMoneySeqAccessMgrTest::testCurrency() +void MyMoneyStorageMgrTest::testCurrency() { MyMoneySecurity curr("EUR", "Euro", "?", 100, 100); MyMoneySecurity newCurr; testAddCurrency(); m->d_func()->m_dirty = false; try { newCurr = m->currency("EUR"); m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); QCOMPARE(newCurr.id(), curr.id()); QCOMPARE(newCurr.name(), curr.name()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { m->currency("DEM"); QFAIL("Expected exception missing"); } catch (const MyMoneyException &) { m->commitTransaction(); m->startTransaction(); QCOMPARE(m->dirty(), false); } } -void MyMoneySeqAccessMgrTest::testCurrencyList() +void MyMoneyStorageMgrTest::testCurrencyList() { QCOMPARE(m->currencyList().count(), 0); testAddCurrency(); QCOMPARE(m->currencyList().count(), 1); MyMoneySecurity unknownCurr("DEM", "Deutsche Mark", "DM", 100, 100); try { m->addCurrency(unknownCurr); m->d_func()->m_dirty = false; QCOMPARE(m->d_func()->m_currencyList.count(), 2); QCOMPARE(m->currencyList().count(), 2); QCOMPARE(m->dirty(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } -void MyMoneySeqAccessMgrTest::testAccountList() +void MyMoneyStorageMgrTest::testAccountList() { QList accounts; m->accountList(accounts); QCOMPARE(accounts.count(), 0); testAddNewAccount(); accounts.clear(); m->accountList(accounts); QCOMPARE(accounts.count(), 2); MyMoneyAccount a = m->account("A000001"); MyMoneyAccount b = m->account("A000002"); m->reparentAccount(b, a); accounts.clear(); m->accountList(accounts); QCOMPARE(accounts.count(), 2); } -void MyMoneySeqAccessMgrTest::testLoaderFunctions() +void MyMoneyStorageMgrTest::testLoaderFunctions() { // we don't need the transaction started by setup() here m->rollbackTransaction(); // account loader QMap amap; MyMoneyAccount acc("A0000176", MyMoneyAccount()); amap[acc.id()] = acc; m->loadAccounts(amap); QCOMPARE(m->d_func()->m_accountList.values(), amap.values()); QCOMPARE(m->d_func()->m_accountList.keys(), amap.keys()); - QCOMPARE(m->accountId(), 176ul); + QCOMPARE(m->d_func()->m_nextAccountID, 176ul); // transaction loader QMap tmap; MyMoneyTransaction t("T000000108", MyMoneyTransaction()); tmap[t.id()] = t; m->loadTransactions(tmap); QCOMPARE(m->d_func()->m_transactionList.values(), tmap.values()); QCOMPARE(m->d_func()->m_transactionList.keys(), tmap.keys()); - QCOMPARE(m->transactionId(), 108ul); + QCOMPARE(m->d_func()->m_nextTransactionID, 108ul); // institution loader QMap imap; MyMoneyInstitution inst("I000028", MyMoneyInstitution()); imap[inst.id()] = inst; m->loadInstitutions(imap); QCOMPARE(m->d_func()->m_institutionList.values(), imap.values()); QCOMPARE(m->d_func()->m_institutionList.keys(), imap.keys()); - QCOMPARE(m->institutionId(), 28ul); + QCOMPARE(m->d_func()->m_nextInstitutionID, 28ul); // payee loader QMap pmap; MyMoneyPayee p("P1234", MyMoneyPayee()); pmap[p.id()] = p; m->loadPayees(pmap); QCOMPARE(m->d_func()->m_payeeList.values(), pmap.values()); QCOMPARE(m->d_func()->m_payeeList.keys(), pmap.keys()); - QCOMPARE(m->payeeId(), 1234ul); + QCOMPARE(m->d_func()->m_nextPayeeID, 1234ul); // tag loader QMap tamap; MyMoneyTag ta("G1234", MyMoneyTag()); tamap[ta.id()] = ta; m->loadTags(tamap); QCOMPARE(m->d_func()->m_tagList.values(), tamap.values()); QCOMPARE(m->d_func()->m_tagList.keys(), tamap.keys()); - QCOMPARE(m->tagId(), 1234ul); + QCOMPARE(m->d_func()->m_nextTagID, 1234ul); // security loader QMap smap; MyMoneySecurity s("S54321", MyMoneySecurity()); smap[s.id()] = s; m->loadSecurities(smap); QCOMPARE(m->d_func()->m_securitiesList.values(), smap.values()); QCOMPARE(m->d_func()->m_securitiesList.keys(), smap.keys()); - QCOMPARE(m->securityId(), 54321ul); + QCOMPARE(m->d_func()->m_nextSecurityID, 54321ul); // schedule loader QMap schmap; MyMoneySchedule sch("SCH6789", MyMoneySchedule()); schmap[sch.id()] = sch; m->loadSchedules(schmap); QCOMPARE(m->d_func()->m_scheduleList.values(), schmap.values()); QCOMPARE(m->d_func()->m_scheduleList.keys(), schmap.keys()); - QCOMPARE(m->scheduleId(), 6789ul); + QCOMPARE(m->d_func()->m_nextScheduleID, 6789ul); // report loader QMap rmap; MyMoneyReport r("R1298", MyMoneyReport()); rmap[r.id()] = r; m->loadReports(rmap); QCOMPARE(m->d_func()->m_reportList.values(), rmap.values()); QCOMPARE(m->d_func()->m_reportList.keys(), rmap.keys()); - QCOMPARE(m->reportId(), 1298ul); + QCOMPARE(m->d_func()->m_nextReportID, 1298ul); // budget loader QMap bmap; MyMoneyBudget b("B89765", MyMoneyBudget()); bmap[b.id()] = b; m->loadBudgets(bmap); QCOMPARE(m->d_func()->m_budgetList.values(), bmap.values()); QCOMPARE(m->d_func()->m_budgetList.keys(), bmap.keys()); - QCOMPARE(m->budgetId(), 89765ul); + QCOMPARE(m->d_func()->m_nextBudgetID, 89765ul); // restart a transaction so that teardown() is happy m->startTransaction(); } -void MyMoneySeqAccessMgrTest::testAddOnlineJob() +void MyMoneyStorageMgrTest::testAddOnlineJob() { // Add a onlineJob onlineJob job(new dummyTask()); m->addOnlineJob(job); QCOMPARE(job.id(), QString("O000001")); m->commitTransaction(); m->startTransaction(); - QCOMPARE(m->onlineJobId(), 1ul); + QCOMPARE(m->d_func()->m_nextOnlineJobID, 1ul); QCOMPARE(m->dirty(), true); QCOMPARE(m->d_func()->m_onlineJobList.count(), 1); QVERIFY(! m->d_func()->m_onlineJobList["O000001"].isNull()); } diff --git a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.h b/kmymoney/mymoney/storage/tests/mymoneystoragemgr-test.h similarity index 87% rename from kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.h rename to kmymoney/mymoney/storage/tests/mymoneystoragemgr-test.h index cabb89501..90eb57054 100644 --- a/kmymoney/mymoney/storage/tests/mymoneyseqaccessmgr-test.h +++ b/kmymoney/mymoney/storage/tests/mymoneystoragemgr-test.h @@ -1,87 +1,87 @@ /*************************************************************************** - mymoneyseqaccessmgrtest.h + mymoneystoragemgrtest.h ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ -#ifndef MYMONEYSEQACCESSMGRTEST_H -#define MYMONEYSEQACCESSMGRTEST_H +#ifndef MYMONEYSTORAGEMGRTEST_H +#define MYMONEYSTORAGEMGRTEST_H #include -#define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneySeqAccessMgrTest; +#define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyStorageMgrTest; #include "mymoneyobject.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" -class MyMoneySeqAccessMgrTest : public QObject +class MyMoneyStorageMgrTest : public QObject { Q_OBJECT public: void testAccount(); protected: - MyMoneySeqAccessMgr *m; + MyMoneyStorageMgr *m; private Q_SLOTS: void init(); void cleanup(); void testEmptyConstructor(); void testSetFunctions(); void testIsStandardAccount(); void testNewAccount(); void testAddNewAccount(); void testAddInstitution(); void testInstitution(); void testAccount2Institution(); void testModifyAccount(); void testModifyInstitution(); void testReparentAccount(); void testAddTransactions(); void testTransactionCount(); void testBalance(); void testModifyTransaction(); void testRemoveUnusedAccount(); void testRemoveUsedAccount(); void testRemoveInstitution(); void testRemoveTransaction(); void testTransactionList(); void testAddPayee(); void testSetAccountName(); void testModifyPayee(); void testPayeeName(); void testRemovePayee(); void testAddTag(); void testModifyTag(); void testTagName(); void testRemoveTag(); void testRemoveAccountFromTree(); // void testAssignment(); - void testEquality(const MyMoneySeqAccessMgr* t); + void testEquality(const MyMoneyStorageMgr* t); // void testDuplicate(); void testAddSchedule(); void testSchedule(); void testModifySchedule(); void testRemoveSchedule(); void testSupportFunctions(); void testScheduleList(); void testAddCurrency(); void testModifyCurrency(); void testRemoveCurrency(); void testCurrency(); void testCurrencyList(); void testAccountList(); void testLoaderFunctions(); void testAddOnlineJob(); }; #endif diff --git a/kmymoney/mymoney/tests/mymoneyfile-test.cpp b/kmymoney/mymoney/tests/mymoneyfile-test.cpp index f7763e54e..a520b7c2f 100644 --- a/kmymoney/mymoney/tests/mymoneyfile-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyfile-test.cpp @@ -1,2780 +1,2780 @@ /*************************************************************************** mymoneyfiletest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyfile-test.h" #include #include #include #include #include #include -#include "mymoneyseqaccessmgr_p.h" +#include "mymoneystoragemgr_p.h" #include "mymoneytestutils.h" #include "mymoneymoney.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneytransaction.h" #include "mymoneytransactionfilter.h" #include "mymoneysplit.h" #include "mymoneyprice.h" #include "mymoneypayee.h" #include "mymoneyenums.h" #include "onlinejob.h" #include "payeeidentifier/ibanandbic/ibanbic.h" #include "payeeidentifier/payeeidentifierloader.h" #include "payeeidentifiertyped.h" #include "mymoneystoragenames.h" using namespace MyMoneyStandardAccounts; QTEST_GUILESS_MAIN(MyMoneyFileTest) void MyMoneyFileTest::objectAdded(eMyMoney::File::Object type, const MyMoneyObject * const obj) { Q_UNUSED(type); m_objectsAdded += obj->id(); } void MyMoneyFileTest::objectRemoved(eMyMoney::File::Object type, const QString& id) { Q_UNUSED(type); m_objectsRemoved += id; } void MyMoneyFileTest::objectModified(eMyMoney::File::Object type, const MyMoneyObject * const obj) { Q_UNUSED(type); m_objectsModified += obj->id(); } void MyMoneyFileTest::clearObjectLists() { m_objectsAdded.clear(); m_objectsModified.clear(); m_objectsRemoved.clear(); m_balanceChanged.clear(); m_valueChanged.clear(); } void MyMoneyFileTest::balanceChanged(const MyMoneyAccount& account) { m_balanceChanged += account.id(); } void MyMoneyFileTest::valueChanged(const MyMoneyAccount& account) { m_valueChanged += account.id(); } // this method will be called once at the beginning of the test void MyMoneyFileTest::initTestCase() { m = MyMoneyFile::instance(); connect(m, SIGNAL(objectAdded(eMyMoney::File::Object,MyMoneyObject*const)), this, SLOT(objectAdded(eMyMoney::File::Object,MyMoneyObject*const))); connect(m, SIGNAL(objectRemoved(eMyMoney::File::Object,QString)), this, SLOT(objectRemoved(eMyMoney::File::Object,QString))); connect(m, SIGNAL(objectModified(eMyMoney::File::Object,MyMoneyObject*const)), this, SLOT(objectModified(eMyMoney::File::Object,MyMoneyObject*const))); connect(m, SIGNAL(balanceChanged(MyMoneyAccount)), this, SLOT(balanceChanged(MyMoneyAccount))); connect(m, SIGNAL(valueChanged(MyMoneyAccount)), this, SLOT(valueChanged(MyMoneyAccount))); } // this method will be called before each testfunction void MyMoneyFileTest::init() { - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; m->attachStorage(storage); clearObjectLists(); } // this method will be called after each testfunction void MyMoneyFileTest::cleanup() { m->detachStorage(storage); delete storage; } void MyMoneyFileTest::testEmptyConstructor() { MyMoneyPayee user = m->user(); QCOMPARE(user.name(), QString()); QCOMPARE(user.address(), QString()); QCOMPARE(user.city(), QString()); QCOMPARE(user.state(), QString()); QCOMPARE(user.postcode(), QString()); QCOMPARE(user.telephone(), QString()); QCOMPARE(user.email(), QString()); QCOMPARE(m->institutionCount(), static_cast(0)); QCOMPARE(m->dirty(), false); QCOMPARE(m->accountCount(), static_cast(5)); } void MyMoneyFileTest::testAddOneInstitution() { MyMoneyInstitution institution; institution.setName("institution1"); institution.setTown("town"); institution.setStreet("street"); institution.setPostcode("postcode"); institution.setTelephone("telephone"); institution.setManager("manager"); institution.setSortcode("sortcode"); // MyMoneyInstitution institution_file("", institution); MyMoneyInstitution institution_id("I000002", institution); MyMoneyInstitution institution_noname(institution); institution_noname.setName(QString()); QString id; QCOMPARE(m->institutionCount(), static_cast(0)); storage->d_func()->m_dirty = false; clearObjectLists(); MyMoneyFileTransaction ft; try { m->addInstitution(institution); ft.commit(); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(m_objectsAdded[0], QLatin1String("I000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } clearObjectLists(); ft.restart(); try { m->addInstitution(institution_id); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); } ft.restart(); try { m->addInstitution(institution_noname); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); } QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); } void MyMoneyFileTest::testAddTwoInstitutions() { testAddOneInstitution(); MyMoneyInstitution institution; institution.setName("institution2"); institution.setTown("town"); institution.setStreet("street"); institution.setPostcode("postcode"); institution.setTelephone("telephone"); institution.setManager("manager"); institution.setSortcode("sortcode"); QString id; storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; try { m->addInstitution(institution); ft.commit(); QCOMPARE(institution.id(), QLatin1String("I000002")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } storage->d_func()->m_dirty = false; try { institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), false); institution = m->institution("I000002"); QCOMPARE(institution.id(), QLatin1String("I000002")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testRemoveInstitution() { testAddTwoInstitutions(); MyMoneyInstitution i; QCOMPARE(m->institutionCount(), static_cast(2)); i = m->institution("I000001"); QCOMPARE(i.id(), QLatin1String("I000001")); QCOMPARE(i.accountCount(), static_cast(0)); clearObjectLists(); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; try { m->removeInstitution(i); QCOMPARE(m_objectsRemoved.count(), 0); ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(m_objectsRemoved[0], QLatin1String("I000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } storage->d_func()->m_dirty = false; try { m->institution("I000001"); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), false); } clearObjectLists(); ft.restart(); try { m->removeInstitution(i); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(m->institutionCount(), static_cast(1)); QCOMPARE(m->dirty(), false); QCOMPARE(m_objectsRemoved.count(), 0); } } void MyMoneyFileTest::testInstitutionRetrieval() { testAddOneInstitution(); storage->d_func()->m_dirty = false; MyMoneyInstitution institution; QCOMPARE(m->institutionCount(), static_cast(1)); try { institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(1)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } try { institution = m->institution("I000002"); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { QCOMPARE(m->institutionCount(), static_cast(1)); } QCOMPARE(m->dirty(), false); } void MyMoneyFileTest::testInstitutionListRetrieval() { QList list; storage->d_func()->m_dirty = false; list = m->institutionList(); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 0); testAddTwoInstitutions(); storage->d_func()->m_dirty = false; list = m->institutionList(); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 2); QList::ConstIterator it; it = list.constBegin(); QVERIFY((*it).name() == "institution1" || (*it).name() == "institution2"); ++it; QVERIFY((*it).name() == "institution2" || (*it).name() == "institution1"); ++it; QVERIFY(it == list.constEnd()); } void MyMoneyFileTest::testInstitutionModify() { testAddTwoInstitutions(); MyMoneyInstitution institution; institution = m->institution("I000001"); institution.setStreet("new street"); institution.setTown("new town"); institution.setPostcode("new postcode"); institution.setTelephone("new telephone"); institution.setManager("new manager"); institution.setName("new name"); institution.setSortcode("new sortcode"); storage->d_func()->m_dirty = false; clearObjectLists(); MyMoneyFileTransaction ft; try { m->modifyInstitution(institution); ft.commit(); QCOMPARE(institution.id(), QLatin1String("I000001")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(m_objectsModified[0], QLatin1String("I000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } MyMoneyInstitution newInstitution; newInstitution = m->institution("I000001"); QCOMPARE(newInstitution.id(), QLatin1String("I000001")); QCOMPARE(newInstitution.street(), QLatin1String("new street")); QCOMPARE(newInstitution.town(), QLatin1String("new town")); QCOMPARE(newInstitution.postcode(), QLatin1String("new postcode")); QCOMPARE(newInstitution.telephone(), QLatin1String("new telephone")); QCOMPARE(newInstitution.manager(), QLatin1String("new manager")); QCOMPARE(newInstitution.name(), QLatin1String("new name")); QCOMPARE(newInstitution.sortcode(), QLatin1String("new sortcode")); storage->d_func()->m_dirty = false; ft.restart(); MyMoneyInstitution failInstitution2("I000003", newInstitution); try { m->modifyInstitution(failInstitution2); QFAIL("Exception expected"); } catch (const MyMoneyException &) { ft.commit(); QCOMPARE(failInstitution2.id(), QLatin1String("I000003")); QCOMPARE(m->institutionCount(), static_cast(2)); QCOMPARE(m->dirty(), false); } } void MyMoneyFileTest::testSetFunctions() { MyMoneyPayee user = m->user(); QCOMPARE(user.name(), QString()); QCOMPARE(user.address(), QString()); QCOMPARE(user.city(), QString()); QCOMPARE(user.state(), QString()); QCOMPARE(user.postcode(), QString()); QCOMPARE(user.telephone(), QString()); QCOMPARE(user.email(), QString()); MyMoneyFileTransaction ft; storage->d_func()->m_dirty = false; user.setName("Name"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setAddress("Street"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setCity("Town"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setState("County"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setPostcode("Postcode"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setTelephone("Telephone"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; user.setEmail("Email"); m->setUser(user); QCOMPARE(m->dirty(), true); storage->d_func()->m_dirty = false; ft.commit(); user = m->user(); QCOMPARE(user.name(), QLatin1String("Name")); QCOMPARE(user.address(), QLatin1String("Street")); QCOMPARE(user.city(), QLatin1String("Town")); QCOMPARE(user.state(), QLatin1String("County")); QCOMPARE(user.postcode(), QLatin1String("Postcode")); QCOMPARE(user.telephone(), QLatin1String("Telephone")); QCOMPARE(user.email(), QLatin1String("Email")); } void MyMoneyFileTest::testAddAccounts() { testAddTwoInstitutions(); MyMoneyAccount a, b, c; a.setAccountType(eMyMoney::Account::Type::Checkings); b.setAccountType(eMyMoney::Account::Type::Checkings); MyMoneyInstitution institution; storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); a.setName("Account1"); a.setInstitutionId(institution.id()); a.setCurrencyId("EUR"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->accountCount(), static_cast(6)); QCOMPARE(a.parentAccountId(), QLatin1String("AStd::Asset")); QCOMPARE(a.id(), QLatin1String("A000001")); QCOMPARE(a.institutionId(), QLatin1String("I000001")); QCOMPARE(a.currencyId(), QLatin1String("EUR")); QCOMPARE(m->dirty(), true); QCOMPARE(m->asset().accountList().count(), 1); QCOMPARE(m->asset().accountList()[0], QLatin1String("A000001")); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(1)); QCOMPARE(institution.accountList()[0], QLatin1String("A000001")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 2); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // try to add this account again, should not work ft.restart(); try { MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); QFAIL("Expecting exception!"); } catch (const MyMoneyException &) { ft.commit(); } // check that we can modify the local object and // reload it from the file a.setName("AccountX"); a = m->account("A000001"); QCOMPARE(a.name(), QLatin1String("Account1")); storage->d_func()->m_dirty = false; // check if we can get the same info to a different object c = m->account("A000001"); QCOMPARE(c.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(c.id(), QLatin1String("A000001")); QCOMPARE(c.name(), QLatin1String("Account1")); QCOMPARE(c.institutionId(), QLatin1String("I000001")); QCOMPARE(m->dirty(), false); // add a second account institution = m->institution("I000002"); b.setName("Account2"); b.setInstitutionId(institution.id()); b.setCurrencyId("EUR"); clearObjectLists(); ft.restart(); try { MyMoneyAccount parent = m->asset(); m->addAccount(b, parent); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(b.id(), QLatin1String("A000002")); QCOMPARE(b.currencyId(), QLatin1String("EUR")); QCOMPARE(b.parentAccountId(), QLatin1String("AStd::Asset")); QCOMPARE(m->accountCount(), static_cast(7)); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(1)); QCOMPARE(institution.accountList()[0], QLatin1String("A000001")); institution = m->institution("I000002"); QCOMPARE(institution.accountCount(), static_cast(1)); QCOMPARE(institution.accountList()[0], QLatin1String("A000002")); QCOMPARE(m->asset().accountList().count(), 2); QCOMPARE(m->asset().accountList()[0], QLatin1String("A000001")); QCOMPARE(m->asset().accountList()[1], QLatin1String("A000002")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 2); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } MyMoneyAccount p; p = m->account("A000002"); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.id(), QLatin1String("A000002")); QCOMPARE(p.name(), QLatin1String("Account2")); QCOMPARE(p.institutionId(), QLatin1String("I000002")); QCOMPARE(p.currencyId(), QLatin1String("EUR")); } void MyMoneyFileTest::testAddCategories() { MyMoneyAccount a, b, c; a.setAccountType(eMyMoney::Account::Type::Income); a.setOpeningDate(QDate::currentDate()); b.setAccountType(eMyMoney::Account::Type::Expense); storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); QCOMPARE(a.openingDate(), QDate::currentDate()); QVERIFY(!b.openingDate().isValid()); a.setName("Account1"); a.setCurrencyId("EUR"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->income(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->accountCount(), static_cast(6)); QCOMPARE(a.parentAccountId(), QLatin1String("AStd::Income")); QCOMPARE(a.id(), QLatin1String("A000001")); QCOMPARE(a.institutionId(), QString()); QCOMPARE(a.currencyId(), QLatin1String("EUR")); QCOMPARE(a.openingDate(), QDate(1900, 1, 1)); QCOMPARE(m->dirty(), true); QCOMPARE(m->income().accountList().count(), 1); QCOMPARE(m->income().accountList()[0], QLatin1String("A000001")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // add a second category, expense this time b.setName("Account2"); b.setCurrencyId("EUR"); clearObjectLists(); ft.restart(); try { MyMoneyAccount parent = m->expense(); m->addAccount(b, parent); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(b.id(), QLatin1String("A000002")); QCOMPARE(a.institutionId(), QString()); QCOMPARE(b.currencyId(), QLatin1String("EUR")); QCOMPARE(b.openingDate(), QDate(1900, 1, 1)); QCOMPARE(b.parentAccountId(), QLatin1String("AStd::Expense")); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(m->income().accountList().count(), 1); QCOMPARE(m->expense().accountList().count(), 1); QCOMPARE(m->income().accountList()[0], QLatin1String("A000001")); QCOMPARE(m->expense().accountList()[0], QLatin1String("A000002")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Expense"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testModifyAccount() { testAddAccounts(); storage->d_func()->m_dirty = false; MyMoneyAccount p = m->account("A000001"); MyMoneyInstitution institution; QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.name(), QLatin1String("Account1")); p.setName("New account name"); MyMoneyFileTransaction ft; clearObjectLists(); try { m->modifyAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.name(), QLatin1String("New account name")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } storage->d_func()->m_dirty = false; // try to move account to new institution p.setInstitutionId("I000002"); ft.restart(); clearObjectLists(); try { m->modifyAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(p.name(), QLatin1String("New account name")); QCOMPARE(p.institutionId(), QLatin1String("I000002")); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(0)); institution = m->institution("I000002"); QCOMPARE(institution.accountCount(), static_cast(2)); QCOMPARE(institution.accountList()[0], QLatin1String("A000002")); QCOMPARE(institution.accountList()[1], QLatin1String("A000001")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 3); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000002"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } storage->d_func()->m_dirty = false; // try to change to an account type that is allowed p.setAccountType(eMyMoney::Account::Type::Savings); ft.restart(); try { m->modifyAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(7)); QCOMPARE(p.accountType(), eMyMoney::Account::Type::Savings); QCOMPARE(p.name(), QLatin1String("New account name")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } storage->d_func()->m_dirty = false; // try to change to an account type that is not allowed p.setAccountType(eMyMoney::Account::Type::CreditCard); ft.restart(); try { m->modifyAccount(p); QFAIL("Expecting exception!"); } catch (const MyMoneyException &) { ft.commit(); } storage->d_func()->m_dirty = false; // try to fool engine a bit p.setParentAccountId("A000001"); ft.restart(); try { m->modifyAccount(p); QFAIL("Expecting exception!"); } catch (const MyMoneyException &) { ft.commit(); } } void MyMoneyFileTest::testReparentAccount() { testAddAccounts(); storage->d_func()->m_dirty = false; MyMoneyAccount p = m->account("A000001"); MyMoneyAccount q = m->account("A000002"); MyMoneyAccount o = m->account(p.parentAccountId()); // make A000001 a child of A000002 clearObjectLists(); MyMoneyFileTransaction ft; try { QVERIFY(p.parentAccountId() != q.id()); QCOMPARE(o.accountCount(), 2); QCOMPARE(q.accountCount(), 0); m->reparentAccount(p, q); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(p.parentAccountId(), q.id()); QCOMPARE(q.accountCount(), 1); QCOMPARE(q.id(), QLatin1String("A000002")); QCOMPARE(p.id(), QLatin1String("A000001")); QCOMPARE(q.accountList()[0], p.id()); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 3); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); o = m->account(o.id()); QCOMPARE(o.accountCount(), 1); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testRemoveStdAccount(const MyMoneyAccount& acc) { QString txt("Exception expected while removing account "); txt += acc.id(); MyMoneyFileTransaction ft; try { m->removeAccount(acc); QFAIL(qPrintable(txt)); } catch (const MyMoneyException &) { ft.commit(); } } void MyMoneyFileTest::testRemoveAccount() { MyMoneyInstitution institution; testAddAccounts(); QCOMPARE(m->accountCount(), static_cast(7)); storage->d_func()->m_dirty = false; QString id; MyMoneyAccount p = m->account("A000001"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount q("Ainvalid", p); m->removeAccount(q); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { ft.commit(); } ft.restart(); try { QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); m->removeAccount(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->accountCount(), static_cast(6)); institution = m->institution("I000001"); QCOMPARE(institution.accountCount(), static_cast(0)); QCOMPARE(m->asset().accountList().count(), 1); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 2); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsRemoved.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); institution = m->institution("I000002"); QCOMPARE(institution.accountCount(), static_cast(1)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // Check that the standard account-groups cannot be removed testRemoveStdAccount(m->liability()); testRemoveStdAccount(m->asset()); testRemoveStdAccount(m->expense()); testRemoveStdAccount(m->income()); } void MyMoneyFileTest::testRemoveAccountTree() { testReparentAccount(); MyMoneyAccount a = m->account("A000002"); clearObjectLists(); MyMoneyFileTransaction ft; // remove the account try { m->removeAccount(a); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 3); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsRemoved.contains(QLatin1String("A000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("A000001"))); QVERIFY(m_objectsModified.contains(QLatin1String("I000002"))); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } QCOMPARE(m->accountCount(), static_cast(6)); // make sure it's gone try { m->account("A000002"); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { } // make sure that children are re-parented to parent account try { a = m->account("A000001"); QCOMPARE(a.parentAccountId(), m->asset().id()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testAccountListRetrieval() { QList list; storage->d_func()->m_dirty = false; m->accountList(list); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 0); testAddAccounts(); storage->d_func()->m_dirty = false; list.clear(); m->accountList(list); QCOMPARE(m->dirty(), false); QCOMPARE(list.count(), 2); QCOMPARE(list[0].accountType(), eMyMoney::Account::Type::Checkings); QCOMPARE(list[1].accountType(), eMyMoney::Account::Type::Checkings); } void MyMoneyFileTest::testAddTransaction() { testAddAccounts(); MyMoneyTransaction t, p; MyMoneyAccount exp1; exp1.setAccountType(eMyMoney::Account::Type::Expense); exp1.setName("Expense1"); MyMoneyAccount exp2; exp2.setAccountType(eMyMoney::Account::Type::Expense); exp2.setName("Expense2"); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->expense(); m->addAccount(exp1, parent); m->addAccount(exp2, parent); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // fake the last modified flag to check that the // date is updated when we add the transaction MyMoneyAccount a = m->account("A000001"); a.setLastModified(QDate(1, 2, 3)); ft.restart(); try { m->modifyAccount(a); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } ft.restart(); QCOMPARE(m->accountCount(), static_cast(9)); a = m->account("A000001"); QCOMPARE(a.lastModified(), QDate(1, 2, 3)); // construct a transaction and add it to the pool t.setPostDate(QDate(2002, 2, 1)); t.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; split1.setAccountId("A000001"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split2.setAccountId("A000003"); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); try { t.addSplit(split1); t.addSplit(split2); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } /* // FIXME: we don't have a payee and a number field right now // guess we should have a number field per split, don't know // about the payee t.setMethod(MyMoneyCheckingTransaction::Withdrawal); t.setPayee("Thomas Baumgart"); t.setNumber("1234"); t.setState(MyMoneyCheckingTransaction::Cleared); */ storage->d_func()->m_dirty = false; ft.restart(); clearObjectLists(); try { m->addTransaction(t); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count("A000001"), 1); QCOMPARE(m_balanceChanged.count("A000003"), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } ft.restart(); clearObjectLists(); QCOMPARE(t.id(), QLatin1String("T000000000000000001")); QCOMPARE(t.postDate(), QDate(2002, 2, 1)); QCOMPARE(t.entryDate(), QDate::currentDate()); QCOMPARE(m->dirty(), true); // check the balance of the accounts a = m->account("A000001"); QCOMPARE(a.lastModified(), QDate::currentDate()); QCOMPARE(a.balance(), MyMoneyMoney(-1000, 100)); MyMoneyAccount b = m->account("A000003"); QCOMPARE(b.lastModified(), QDate::currentDate()); QCOMPARE(b.balance(), MyMoneyMoney(1000, 100)); storage->d_func()->m_dirty = false; // locate transaction in MyMoneyFile via id try { p = m->transaction("T000000000000000001"); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(p.splitCount(), static_cast(2)); QCOMPARE(p.memo(), QLatin1String("Memotext")); QCOMPARE(p.splits()[0].accountId(), QLatin1String("A000001")); QCOMPARE(p.splits()[1].accountId(), QLatin1String("A000003")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // check if it's in the account(s) as well try { p = m->transaction("A000001", 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(p.id(), QLatin1String("T000000000000000001")); QCOMPARE(p.splitCount(), static_cast(2)); QCOMPARE(p.memo(), QLatin1String("Memotext")); QCOMPARE(p.splits()[0].accountId(), QLatin1String("A000001")); QCOMPARE(p.splits()[1].accountId(), QLatin1String("A000003")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } try { p = m->transaction("A000003", 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QCOMPARE(p.id(), QLatin1String("T000000000000000001")); QCOMPARE(p.splitCount(), static_cast(2)); QCOMPARE(p.memo(), QLatin1String("Memotext")); QCOMPARE(p.splits()[0].accountId(), QLatin1String("A000001")); QCOMPARE(p.splits()[1].accountId(), QLatin1String("A000003")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testIsStandardAccount() { QCOMPARE(m->isStandardAccount(m->liability().id()), true); QCOMPARE(m->isStandardAccount(m->asset().id()), true); QCOMPARE(m->isStandardAccount(m->expense().id()), true); QCOMPARE(m->isStandardAccount(m->income().id()), true); QCOMPARE(m->isStandardAccount("A00001"), false); } void MyMoneyFileTest::testHasActiveSplits() { testAddTransaction(); QCOMPARE(m->hasActiveSplits("A000001"), true); QCOMPARE(m->hasActiveSplits("A000002"), false); } void MyMoneyFileTest::testModifyTransactionSimple() { // this will test that we can modify the basic attributes // of a transaction testAddTransaction(); MyMoneyTransaction t = m->transaction("T000000000000000001"); t.setMemo("New Memotext"); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { m->modifyTransaction(t); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); t = m->transaction("T000000000000000001"); QCOMPARE(t.memo(), QLatin1String("New Memotext")); QCOMPARE(m->dirty(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testModifyTransactionNewPostDate() { // this will test that we can modify the basic attributes // of a transaction testAddTransaction(); MyMoneyTransaction t = m->transaction("T000000000000000001"); t.setPostDate(QDate(2004, 2, 1)); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { m->modifyTransaction(t); ft.commit(); t = m->transaction("T000000000000000001"); QCOMPARE(t.postDate(), QDate(2004, 2, 1)); t = m->transaction("A000001", 0); QCOMPARE(t.id(), QLatin1String("T000000000000000001")); QCOMPARE(m->dirty(), true); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testModifyTransactionNewAccount() { // this will test that we can modify the basic attributes // of a transaction testAddTransaction(); MyMoneyTransaction t = m->transaction("T000000000000000001"); MyMoneySplit s; s = t.splits()[0]; s.setAccountId("A000002"); t.modifySplit(s); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { MyMoneyTransactionFilter f1("A000001"); MyMoneyTransactionFilter f2("A000002"); MyMoneyTransactionFilter f3("A000003"); QCOMPARE(m->transactionList(f1).count(), 1); QCOMPARE(m->transactionList(f2).count(), 0); QCOMPARE(m->transactionList(f3).count(), 1); m->modifyTransaction(t); ft.commit(); t = m->transaction("T000000000000000001"); QCOMPARE(t.postDate(), QDate(2002, 2, 1)); t = m->transaction("A000002", 0); QCOMPARE(m->dirty(), true); QCOMPARE(m->transactionList(f1).count(), 0); QCOMPARE(m->transactionList(f2).count(), 1); QCOMPARE(m->transactionList(f3).count(), 1); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 3); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000002")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testRemoveTransaction() { testModifyTransactionNewPostDate(); MyMoneyTransaction t; t = m->transaction("T000000000000000001"); storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; clearObjectLists(); try { m->removeTransaction(t); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(m->transactionCount(), static_cast(0)); MyMoneyTransactionFilter f1("A000001"); MyMoneyTransactionFilter f2("A000002"); MyMoneyTransactionFilter f3("A000003"); QCOMPARE(m->transactionList(f1).count(), 0); QCOMPARE(m->transactionList(f2).count(), 0); QCOMPARE(m->transactionList(f3).count(), 0); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 2); QCOMPARE(m_balanceChanged.count(QLatin1String("A000001")), 1); QCOMPARE(m_balanceChanged.count(QLatin1String("A000003")), 1); QCOMPARE(m_valueChanged.count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } /* * This function is currently not implemented. It's kind of tricky * because it modifies a lot of objects in a single call. This might * be a problem for the undo/redo stuff. That's why I left it out in * the first run. We migh add it, if we need it. * / void testMoveSplits() { testModifyTransactionNewPostDate(); QCOMPARE(m->account("A000001").transactionCount(), 1); QCOMPARE(m->account("A000002").transactionCount(), 0); QCOMPARE(m->account("A000003").transactionCount(), 1); try { m->moveSplits("A000001", "A000002"); QCOMPARE(m->account("A000001").transactionCount(), 0); QCOMPARE(m->account("A000002").transactionCount(), 1); QCOMPARE(m->account("A000003").transactionCount(), 1); } catch(const MyMoneyException &e) { QFAIL("Unexpected exception!"); } } */ void MyMoneyFileTest::testBalanceTotal() { testAddTransaction(); MyMoneyTransaction t; // construct a transaction and add it to the pool t.setPostDate(QDate(2002, 2, 1)); t.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; MyMoneyFileTransaction ft; try { split1.setAccountId("A000002"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split2.setAccountId("A000004"); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); t.addSplit(split1); t.addSplit(split2); m->addTransaction(t); ft.commit(); ft.restart(); QCOMPARE(t.id(), QLatin1String("T000000000000000002")); QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(-1000, 100)); QCOMPARE(m->totalBalance("A000002"), MyMoneyMoney(-1000, 100)); MyMoneyAccount p = m->account("A000001"); MyMoneyAccount q = m->account("A000002"); m->reparentAccount(p, q); ft.commit(); // check totalBalance() and balance() with combinations of parameters QCOMPARE(m->totalBalance("A000001"), MyMoneyMoney(-1000, 100)); QCOMPARE(m->totalBalance("A000002"), MyMoneyMoney(-2000, 100)); QVERIFY(m->totalBalance("A000002", QDate(2002, 1, 15)).isZero()); QCOMPARE(m->balance("A000001"), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002"), MyMoneyMoney(-1000, 100)); // Date of a transaction QCOMPARE(m->balance("A000001", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); // Date after last transaction QCOMPARE(m->balance("A000001", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); // Date before first transaction QVERIFY(m->balance("A000001", QDate(2002, 1, 15)).isZero()); QVERIFY(m->balance("A000002", QDate(2002, 1, 15)).isZero()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // Now check for exceptions try { // Account not found for balance() QVERIFY(m->balance("A000005").isZero()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } try { // Account not found for totalBalance() QVERIFY(m->totalBalance("A000005").isZero()); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyFileTest::testSetAccountName() { MyMoneyFileTransaction ft; clearObjectLists(); try { m->setAccountName(stdAccNames[stdAccLiability], "Verbindlichkeiten"); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Liability"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); clearObjectLists(); try { m->setAccountName(stdAccNames[stdAccAsset], QString::fromUtf8("Vermögen")); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); clearObjectLists(); try { m->setAccountName(stdAccNames[stdAccExpense], "Ausgaben"); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Expense"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); clearObjectLists(); try { m->setAccountName(stdAccNames[stdAccIncome], "Einnahmen"); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Income"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } ft.restart(); QCOMPARE(m->liability().name(), QLatin1String("Verbindlichkeiten")); QCOMPARE(m->asset().name(), QString::fromUtf8("Vermögen")); QCOMPARE(m->expense().name(), QLatin1String("Ausgaben")); QCOMPARE(m->income().name(), QLatin1String("Einnahmen")); try { m->setAccountName("A000001", "New account name"); ft.commit(); QFAIL("Exception expected"); } catch (const MyMoneyException &) { } } void MyMoneyFileTest::testAddPayee() { MyMoneyPayee p; p.setName("THB"); QCOMPARE(m->dirty(), false); MyMoneyFileTransaction ft; try { m->addPayee(p); ft.commit(); QCOMPARE(m->dirty(), true); QCOMPARE(p.id(), QLatin1String("P000001")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(QLatin1String("P000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testModifyPayee() { MyMoneyPayee p; testAddPayee(); clearObjectLists(); p = m->payee("P000001"); p.setName("New name"); MyMoneyFileTransaction ft; try { m->modifyPayee(p); ft.commit(); p = m->payee("P000001"); QCOMPARE(p.name(), QLatin1String("New name")); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsModified.contains(QLatin1String("P000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testRemovePayee() { MyMoneyPayee p; testAddPayee(); clearObjectLists(); QCOMPARE(m->payeeList().count(), 1); p = m->payee("P000001"); MyMoneyFileTransaction ft; try { m->removePayee(p); ft.commit(); QCOMPARE(m->payeeList().count(), 0); QCOMPARE(m_objectsRemoved.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsRemoved.contains(QLatin1String("P000001"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyFileTest::testPayeeWithIdentifier() { MyMoneyPayee p; try { MyMoneyFileTransaction ft; m->addPayee(p); ft.commit(); p = m->payee(p.id()); payeeIdentifier ident = payeeIdentifierLoader::instance()->createPayeeIdentifier(payeeIdentifiers::ibanBic::staticPayeeIdentifierIid()); payeeIdentifierTyped iban(ident); iban->setIban(QLatin1String("DE82 2007 0024 0066 6446 00")); ft.restart(); p.addPayeeIdentifier(iban); m->modifyPayee(p); ft.commit(); p = m->payee(p.id()); QCOMPARE(p.payeeIdentifiers().count(), 1); ident = p.payeeIdentifiers().first(); try { iban = payeeIdentifierTyped(ident); } catch (...) { QFAIL("Unexpected exception"); } QCOMPARE(iban->electronicIban(), QLatin1String("DE82200700240066644600")); } catch (const MyMoneyException& e) { unexpectedException(e); } } void MyMoneyFileTest::testAddTransactionStd() { testAddAccounts(); MyMoneyTransaction t, p; MyMoneyAccount a; a = m->account("A000001"); // construct a transaction and add it to the pool t.setPostDate(QDate(2002, 2, 1)); t.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; split1.setAccountId("A000001"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split2.setAccountId(stdAccNames[stdAccExpense]); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); try { t.addSplit(split1); t.addSplit(split2); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } /* // FIXME: we don't have a payee and a number field right now // guess we should have a number field per split, don't know // about the payee t.setMethod(MyMoneyCheckingTransaction::Withdrawal); t.setPayee("Thomas Baumgart"); t.setNumber("1234"); t.setState(MyMoneyCheckingTransaction::Cleared); */ storage->d_func()->m_dirty = false; MyMoneyFileTransaction ft; try { m->addTransaction(t); ft.commit(); QFAIL("Missing expected exception!"); } catch (const MyMoneyException &) { } QCOMPARE(m->dirty(), false); } void MyMoneyFileTest::testAttachStorage() { - IMyMoneyStorage *store = new MyMoneySeqAccessMgr; + MyMoneyStorageMgr *store = new MyMoneyStorageMgr; MyMoneyFile *file = new MyMoneyFile; QCOMPARE(file->storageAttached(), false); try { file->attachStorage(store); QCOMPARE(file->storageAttached(), true); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } try { file->attachStorage(store); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { } try { file->attachStorage(0); QFAIL("Exception expected!"); } catch (const MyMoneyException &) { } try { file->detachStorage(store); QCOMPARE(file->storageAttached(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } delete store; delete file; } void MyMoneyFileTest::testAccount2Category() { testReparentAccount(); QCOMPARE(m->accountToCategory("A000001"), QLatin1String("Account2:Account1")); QCOMPARE(m->accountToCategory("A000002"), QLatin1String("Account2")); } void MyMoneyFileTest::testCategory2Account() { testAddTransaction(); MyMoneyAccount a = m->account("A000003"); MyMoneyAccount b = m->account("A000004"); MyMoneyFileTransaction ft; try { m->reparentAccount(b, a); ft.commit(); QCOMPARE(m->categoryToAccount("Expense1"), QLatin1String("A000003")); QCOMPARE(m->categoryToAccount("Expense1:Expense2"), QLatin1String("A000004")); QVERIFY(m->categoryToAccount("Acc2").isEmpty()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testAttachedStorage() { QCOMPARE(m->storageAttached(), true); QVERIFY(m->storage() != 0); - IMyMoneyStorage *p = m->storage(); + MyMoneyStorageMgr *p = m->storage(); m->detachStorage(p); QCOMPARE(m->storageAttached(), false); - QCOMPARE(m->storage(), static_cast(0)); + QCOMPARE(m->storage(), static_cast(0)); m->attachStorage(p); QCOMPARE(m->storageAttached(), true); QVERIFY(m->storage() != 0); } void MyMoneyFileTest::testHasAccount() { testAddAccounts(); MyMoneyAccount a, b; a.setAccountType(eMyMoney::Account::Type::Checkings); a.setName("Account3"); b = m->account("A000001"); MyMoneyFileTransaction ft; try { m->addAccount(a, b); ft.commit(); QCOMPARE(m->accountCount(), static_cast(8)); QCOMPARE(a.parentAccountId(), QLatin1String("A000001")); QCOMPARE(m->hasAccount("A000001", "Account3"), true); QCOMPARE(m->hasAccount("A000001", "Account2"), false); QCOMPARE(m->hasAccount("A000002", "Account3"), false); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testAddEquityAccount() { MyMoneyAccount i; i.setName("Investment"); i.setAccountType(eMyMoney::Account::Type::Investment); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->asset(); m->addAccount(i, parent); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } // keep a copy for later use m_inv = i; // make sure, that only equity accounts can be children to it MyMoneyAccount a; a.setName("Testaccount"); QList list; list << eMyMoney::Account::Type::Checkings; list << eMyMoney::Account::Type::Savings; list << eMyMoney::Account::Type::Cash; list << eMyMoney::Account::Type::CreditCard; list << eMyMoney::Account::Type::Loan; list << eMyMoney::Account::Type::CertificateDep; list << eMyMoney::Account::Type::Investment; list << eMyMoney::Account::Type::MoneyMarket; list << eMyMoney::Account::Type::Asset; list << eMyMoney::Account::Type::Liability; list << eMyMoney::Account::Type::Currency; list << eMyMoney::Account::Type::Income; list << eMyMoney::Account::Type::Expense; list << eMyMoney::Account::Type::AssetLoan; QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { a.setAccountType(*it); ft.restart(); try { char msg[100]; m->addAccount(a, i); sprintf(msg, "Can add non-equity type %d to investment", (int)*it); QFAIL(msg); } catch (const MyMoneyException &) { ft.commit(); } } ft.restart(); try { a.setName("Teststock"); a.setAccountType(eMyMoney::Account::Type::Stock); m->addAccount(a, i); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testReparentEquity() { testAddEquityAccount(); testAddEquityAccount(); MyMoneyAccount parent; // check the bad cases QList list; list << eMyMoney::Account::Type::Checkings; list << eMyMoney::Account::Type::Savings; list << eMyMoney::Account::Type::Cash; list << eMyMoney::Account::Type::CertificateDep; list << eMyMoney::Account::Type::MoneyMarket; list << eMyMoney::Account::Type::Asset; list << eMyMoney::Account::Type::AssetLoan; list << eMyMoney::Account::Type::Currency; parent = m->asset(); testReparentEquity(list, parent); list.clear(); list << eMyMoney::Account::Type::CreditCard; list << eMyMoney::Account::Type::Loan; list << eMyMoney::Account::Type::Liability; parent = m->liability(); testReparentEquity(list, parent); list.clear(); list << eMyMoney::Account::Type::Income; parent = m->income(); testReparentEquity(list, parent); list.clear(); list << eMyMoney::Account::Type::Expense; parent = m->expense(); testReparentEquity(list, parent); // now check the good case MyMoneyAccount stock = m->account("A000002"); MyMoneyAccount inv = m->account(m_inv.id()); MyMoneyFileTransaction ft; try { m->reparentAccount(stock, inv); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testReparentEquity(QList& list, MyMoneyAccount& parent) { MyMoneyAccount a; MyMoneyAccount stock = m->account("A000002"); QList::Iterator it; MyMoneyFileTransaction ft; for (it = list.begin(); it != list.end(); ++it) { a.setName(QString("Testaccount %1").arg((int)*it)); a.setAccountType(*it); try { m->addAccount(a, parent); char msg[100]; m->reparentAccount(stock, a); sprintf(msg, "Can reparent stock to non-investment type %d account", (int)*it); QFAIL(msg); } catch (const MyMoneyException &) { ft.commit(); } ft.restart(); } } void MyMoneyFileTest::testBaseCurrency() { MyMoneySecurity base("EUR", "Euro", QChar(0x20ac)); MyMoneySecurity ref; // make sure, no base currency is set try { ref = m->baseCurrency(); QVERIFY(ref.id().isEmpty()); } catch (const MyMoneyException &e) { unexpectedException(e); } // make sure, we cannot assign an unknown currency try { m->setBaseCurrency(base); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } MyMoneyFileTransaction ft; // add the currency and try again try { m->addCurrency(base); m->setBaseCurrency(base); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } ft.restart(); // make sure, the base currency is set try { ref = m->baseCurrency(); QCOMPARE(ref.id(), QLatin1String("EUR")); QCOMPARE(ref.name(), QLatin1String("Euro")); QVERIFY(ref.tradingSymbol() == QChar(0x20ac)); } catch (const MyMoneyException &e) { unexpectedException(e); } // check if it gets reset when attaching a new storage m->detachStorage(storage); - MyMoneySeqAccessMgr* newStorage = new MyMoneySeqAccessMgr; + MyMoneyStorageMgr* newStorage = new MyMoneyStorageMgr; m->attachStorage(newStorage); ref = m->baseCurrency(); QVERIFY(ref.id().isEmpty()); m->detachStorage(newStorage); delete newStorage; m->attachStorage(storage); ref = m->baseCurrency(); QCOMPARE(ref.id(), QLatin1String("EUR")); QCOMPARE(ref.name(), QLatin1String("Euro")); QVERIFY(ref.tradingSymbol() == QChar(0x20ac)); } void MyMoneyFileTest::testOpeningBalanceNoBase() { MyMoneyAccount openingAcc; MyMoneySecurity base; try { base = m->baseCurrency(); openingAcc = m->openingBalanceAccount(base); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } } void MyMoneyFileTest::testOpeningBalance() { MyMoneyAccount openingAcc; MyMoneySecurity second("USD", "US Dollar", "$"); testBaseCurrency(); try { openingAcc = m->openingBalanceAccount(m->baseCurrency()); QCOMPARE(openingAcc.parentAccountId(), m->equity().id()); QCOMPARE(openingAcc.name(), MyMoneyFile::openingBalancesPrefix()); QCOMPARE(openingAcc.openingDate(), QDate::currentDate()); } catch (const MyMoneyException &e) { unexpectedException(e); } // add a second currency MyMoneyFileTransaction ft; try { m->addCurrency(second); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } QString refName = QString("%1 (%2)").arg(MyMoneyFile::openingBalancesPrefix()).arg("USD"); try { openingAcc = m->openingBalanceAccount(second); QCOMPARE(openingAcc.parentAccountId(), m->equity().id()); QCOMPARE(openingAcc.name(), refName); QCOMPARE(openingAcc.openingDate(), QDate::currentDate()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testModifyStdAccount() { QVERIFY(m->asset().currencyId().isEmpty()); QCOMPARE(m->asset().name(), QLatin1String("Asset")); testBaseCurrency(); QVERIFY(m->asset().currencyId().isEmpty()); QVERIFY(!m->baseCurrency().id().isEmpty()); MyMoneyFileTransaction ft; try { MyMoneyAccount acc = m->asset(); acc.setName("Anlagen"); acc.setCurrencyId(m->baseCurrency().id()); m->modifyAccount(acc); ft.commit(); QCOMPARE(m->asset().name(), QLatin1String("Anlagen")); QCOMPARE(m->asset().currencyId(), m->baseCurrency().id()); } catch (const MyMoneyException &e) { unexpectedException(e); } ft.restart(); try { MyMoneyAccount acc = m->asset(); acc.setNumber("Test"); m->modifyAccount(acc); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { ft.rollback(); } } void MyMoneyFileTest::testAddPrice() { testAddAccounts(); testBaseCurrency(); MyMoneyAccount p; MyMoneyFileTransaction ft; try { p = m->account("A000002"); p.setCurrencyId("RON"); m->modifyAccount(p); ft.commit(); QCOMPARE(m->account("A000002").currencyId(), QLatin1String("RON")); } catch (const MyMoneyException &e) { unexpectedException(e); } clearObjectLists(); ft.restart(); MyMoneyPrice price("EUR", "RON", QDate::currentDate(), MyMoneyMoney(4.1), "Test source"); m->addPrice(price); ft.commit(); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 1); QCOMPARE(m_valueChanged.count("A000002"), 1); clearObjectLists(); ft.restart(); MyMoneyPrice priceReciprocal("RON", "EUR", QDate::currentDate(), MyMoneyMoney(1 / 4.1), "Test source reciprocal price"); m->addPrice(priceReciprocal); ft.commit(); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 1); QCOMPARE(m_valueChanged.count("A000002"), 1); } void MyMoneyFileTest::testRemovePrice() { testAddPrice(); clearObjectLists(); MyMoneyFileTransaction ft; MyMoneyPrice price("EUR", "RON", QDate::currentDate(), MyMoneyMoney(4.1), "Test source"); m->removePrice(price); ft.commit(); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 1); QCOMPARE(m_valueChanged.count("A000002"), 1); } void MyMoneyFileTest::testGetPrice() { testAddPrice(); // the price for the current date is found QVERIFY(m->price("EUR", "RON", QDate::currentDate()).isValid()); // the price for the current date is returned when asking for the next day with exact date set to false { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(1), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate()); } // no price is returned while asking for the next day with exact date set to true QVERIFY(!m->price("EUR", "RON", QDate::currentDate().addDays(1), true).isValid()); // no price is returned while asking for the previous day with exact date set to true/false because all prices are newer QVERIFY(!m->price("EUR", "RON", QDate::currentDate().addDays(-1), false).isValid()); QVERIFY(!m->price("EUR", "RON", QDate::currentDate().addDays(-1), true).isValid()); // add two more prices MyMoneyFileTransaction ft; m->addPrice(MyMoneyPrice("EUR", "RON", QDate::currentDate().addDays(3), MyMoneyMoney(4.1), "Test source")); m->addPrice(MyMoneyPrice("EUR", "RON", QDate::currentDate().addDays(5), MyMoneyMoney(4.1), "Test source")); ft.commit(); clearObjectLists(); // extra tests for the exactDate=false behavior { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(2), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate()); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(3), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(3)); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(4), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(3)); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(5), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(5)); } { const MyMoneyPrice &price = m->price("EUR", "RON", QDate::currentDate().addDays(6), false); QVERIFY(price.isValid() && price.date() == QDate::currentDate().addDays(5)); } } void MyMoneyFileTest::testAddAccountMissingCurrency() { testAddTwoInstitutions(); MyMoneySecurity base("EUR", "Euro", QChar(0x20ac)); MyMoneyAccount a; a.setAccountType(eMyMoney::Account::Type::Checkings); MyMoneyInstitution institution; storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); institution = m->institution("I000001"); QCOMPARE(institution.id(), QLatin1String("I000001")); a.setName("Account1"); a.setInstitutionId(institution.id()); clearObjectLists(); MyMoneyFileTransaction ft; try { m->addCurrency(base); m->setBaseCurrency(base); MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->account("A000001").currencyId(), QLatin1String("EUR")); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testAddTransactionToClosedAccount() { QSKIP("Test not implemented yet", SkipAll); } void MyMoneyFileTest::testRemoveTransactionFromClosedAccount() { QSKIP("Test not implemented yet", SkipAll); } void MyMoneyFileTest::testModifyTransactionInClosedAccount() { QSKIP("Test not implemented yet", SkipAll); } void MyMoneyFileTest::testStorageId() { QString id; // make sure id will be setup if it does not exist MyMoneyFileTransaction ft; try { m->setValue("kmm-id", ""); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } try { // check for a new id id = m->storageId(); QVERIFY(!id.isEmpty()); // check that it is the same if we ask again QCOMPARE(id, m->storageId()); } catch (const MyMoneyException &e) { unexpectedException(e); } } void MyMoneyFileTest::testHasMatchingOnlineBalance_emptyAccountWithoutImportedBalance() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); QCOMPARE(m->hasMatchingOnlineBalance(a), false); } void MyMoneyFileTest::testHasMatchingOnlineBalance_emptyAccountWithEqualImportedBalance() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); a.setValue("lastImportedTransactionDate", QDate(2011, 12, 1).toString(Qt::ISODate)); a.setValue("lastStatementBalance", MyMoneyMoney().toString()); MyMoneyFileTransaction ft; m->modifyAccount(a); ft.commit(); QCOMPARE(m->hasMatchingOnlineBalance(a), true); } void MyMoneyFileTest::testHasMatchingOnlineBalance_emptyAccountWithUnequalImportedBalance() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); a.setValue("lastImportedTransactionDate", QDate(2011, 12, 1).toString(Qt::ISODate)); a.setValue("lastStatementBalance", MyMoneyMoney::ONE.toString()); MyMoneyFileTransaction ft; m->modifyAccount(a); ft.commit(); QCOMPARE(m->hasMatchingOnlineBalance(a), false); } void MyMoneyFileTest::testHasNewerTransaction_withoutAnyTransaction_afterLastImportedTransaction() { AddOneAccount(); MyMoneyAccount a = m->account("A000001"); QDate dateOfLastTransactionImport(2011, 12, 1); // There are no transactions at all: QCOMPARE(m->hasNewerTransaction(a.id(), dateOfLastTransactionImport), false); } void MyMoneyFileTest::testHasNewerTransaction_withoutNewerTransaction_afterLastImportedTransaction() { AddOneAccount(); QString accId("A000001"); QDate dateOfLastTransactionImport(2011, 12, 1); MyMoneyFileTransaction ft; MyMoneyTransaction t; // construct a transaction at the day of the last transaction import and add it to the pool t.setPostDate(dateOfLastTransactionImport); MyMoneySplit split1; split1.setAccountId(accId); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); t.addSplit(split1); ft.restart(); m->addTransaction(t); ft.commit(); QCOMPARE(m->hasNewerTransaction(accId, dateOfLastTransactionImport), false); } void MyMoneyFileTest::testHasNewerTransaction_withNewerTransaction_afterLastImportedTransaction() { AddOneAccount(); QString accId("A000001"); QDate dateOfLastTransactionImport(2011, 12, 1); QDate dateOfDayAfterLastTransactionImport(dateOfLastTransactionImport.addDays(1)); MyMoneyFileTransaction ft; MyMoneyTransaction t; // construct a transaction a day after the last transaction import and add it to the pool t.setPostDate(dateOfDayAfterLastTransactionImport); MyMoneySplit split1; split1.setAccountId(accId); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); t.addSplit(split1); ft.restart(); m->addTransaction(t); ft.commit(); QCOMPARE(m->hasNewerTransaction(accId, dateOfLastTransactionImport), true); } void MyMoneyFileTest::AddOneAccount() { QString accountId = "A000001"; MyMoneyAccount a; a.setAccountType(eMyMoney::Account::Type::Checkings); storage->d_func()->m_dirty = false; QCOMPARE(m->accountCount(), static_cast(5)); a.setName("Account1"); a.setCurrencyId("EUR"); clearObjectLists(); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->asset(); m->addAccount(a, parent); ft.commit(); QCOMPARE(m->accountCount(), static_cast(6)); QCOMPARE(a.parentAccountId(), QLatin1String("AStd::Asset")); QCOMPARE(a.id(), accountId); QCOMPARE(a.currencyId(), QLatin1String("EUR")); QCOMPARE(m->dirty(), true); QCOMPARE(m->asset().accountList().count(), 1); QCOMPARE(m->asset().accountList()[0], accountId); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); QVERIFY(m_objectsAdded.contains(accountId.toLatin1())); QVERIFY(m_objectsModified.contains(QLatin1String("AStd::Asset"))); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testCountTransactionsWithSpecificReconciliationState_noTransactions() { AddOneAccount(); QString accountId = "A000001"; QCOMPARE(m->countTransactionsWithSpecificReconciliationState(accountId, eMyMoney::TransactionFilter::State::NotReconciled), 0); } void MyMoneyFileTest::testCountTransactionsWithSpecificReconciliationState_transactionWithWantedReconcileState() { AddOneAccount(); QString accountId = "A000001"; // construct split & transaction MyMoneySplit split; split.setAccountId(accountId); split.setShares(MyMoneyMoney(-1000, 100)); split.setValue(MyMoneyMoney(-1000, 100)); MyMoneyTransaction transaction; transaction.setPostDate(QDate(2013, 1, 1)); transaction.addSplit(split); // add transaction MyMoneyFileTransaction ft; m->addTransaction(transaction); ft.commit(); QCOMPARE(m->countTransactionsWithSpecificReconciliationState(accountId, eMyMoney::TransactionFilter::State::NotReconciled), 1); } void MyMoneyFileTest::testCountTransactionsWithSpecificReconciliationState_transactionWithUnwantedReconcileState() { AddOneAccount(); QString accountId = "A000001"; // construct split & transaction MyMoneySplit split; split.setAccountId(accountId); split.setShares(MyMoneyMoney(-1000, 100)); split.setValue(MyMoneyMoney(-1000, 100)); split.setReconcileFlag(eMyMoney::Split::State::Reconciled); MyMoneyTransaction transaction; transaction.setPostDate(QDate(2013, 1, 1)); transaction.addSplit(split); // add transaction MyMoneyFileTransaction ft; m->addTransaction(transaction); ft.commit(); QCOMPARE(m->countTransactionsWithSpecificReconciliationState(accountId, eMyMoney::TransactionFilter::State::NotReconciled), 0); } void MyMoneyFileTest::testAddOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); QCOMPARE(job.id(), QString("O000001")); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 1); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); #endif } void MyMoneyFileTest::testGetOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); testAddOnlineJob(); const onlineJob requestedJob = m->getOnlineJob("O000001"); QVERIFY(!requestedJob.isNull()); QCOMPARE(requestedJob.id(), QString("O000001")); } void MyMoneyFileTest::testRemoveOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); onlineJob job2(new germanOnlineTransfer()); onlineJob job3(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); m->addOnlineJob(job2); m->addOnlineJob(job3); ft.commit(); clearObjectLists(); ft.restart(); m->removeOnlineJob(job); m->removeOnlineJob(job2); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 2); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); #endif } void MyMoneyFileTest::testOnlineJobRollback() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); onlineJob job2(new germanOnlineTransfer()); onlineJob job3(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); m->addOnlineJob(job2); m->addOnlineJob(job3); ft.rollback(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 0); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); #endif } void MyMoneyFileTest::testRemoveLockedOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); job.setLock(true); QVERIFY(job.isLocked()); MyMoneyFileTransaction ft; m->addOnlineJob(job); ft.commit(); clearObjectLists(); // Try removing locked transfer ft.restart(); m->removeOnlineJob(job); ft.commit(); QVERIFY2(m_objectsRemoved.count() == 0, "Online Job was locked, removing is not allowed"); QVERIFY(m_objectsAdded.count() == 0); QVERIFY(m_objectsModified.count() == 0); QVERIFY(m_balanceChanged.count() == 0); QVERIFY(m_valueChanged.count() == 0); #endif } /** @todo */ void MyMoneyFileTest::testModifyOnlineJob() { QSKIP("Need dummy task for this test", SkipAll); #if 0 // Add a onlineJob onlineJob job(new germanOnlineTransfer()); MyMoneyFileTransaction ft; m->addOnlineJob(job); ft.commit(); clearObjectLists(); // Modify online job job.setJobSend(); ft.restart(); m->modifyOnlineJob(job); ft.commit(); QCOMPARE(m_objectsRemoved.count(), 0); QCOMPARE(m_objectsAdded.count(), 0); QCOMPARE(m_objectsModified.count(), 1); QCOMPARE(m_balanceChanged.count(), 0); QCOMPARE(m_valueChanged.count(), 0); //onlineJob modifiedJob = m->getOnlineJob( job.id() ); //QCOMPARE(modifiedJob.responsibleAccount(), QString("Std::Assert")); #endif } void MyMoneyFileTest::testClearedBalance() { testAddTransaction(); MyMoneyTransaction t1; MyMoneyTransaction t2; // construct a transaction and add it to the pool t1.setPostDate(QDate(2002, 2, 1)); t1.setMemo("Memotext"); t2.setPostDate(QDate(2002, 2, 4)); t2.setMemo("Memotext"); MyMoneySplit split1; MyMoneySplit split2; MyMoneySplit split3; MyMoneySplit split4; MyMoneyFileTransaction ft; try { split1.setAccountId("A000002"); split1.setShares(MyMoneyMoney(-1000, 100)); split1.setValue(MyMoneyMoney(-1000, 100)); split1.setReconcileFlag(eMyMoney::Split::State::Cleared); split2.setAccountId("A000004"); split2.setValue(MyMoneyMoney(1000, 100)); split2.setShares(MyMoneyMoney(1000, 100)); split2.setReconcileFlag(eMyMoney::Split::State::Cleared); t1.addSplit(split1); t1.addSplit(split2); m->addTransaction(t1); ft.commit(); ft.restart(); QCOMPARE(t1.id(), QLatin1String("T000000000000000002")); split3.setAccountId("A000002"); split3.setShares(MyMoneyMoney(-2000, 100)); split3.setValue(MyMoneyMoney(-2000, 100)); split3.setReconcileFlag(eMyMoney::Split::State::Cleared); split4.setAccountId("A000004"); split4.setValue(MyMoneyMoney(2000, 100)); split4.setShares(MyMoneyMoney(2000, 100)); split4.setReconcileFlag(eMyMoney::Split::State::Cleared); t2.addSplit(split3); t2.addSplit(split4); m->addTransaction(t2); ft.commit(); ft.restart(); QCOMPARE(m->balance("A000001", QDate(2002, 2, 4)), MyMoneyMoney(-1000, 100)); QCOMPARE(m->balance("A000002", QDate(2002, 2, 4)), MyMoneyMoney(-3000, 100)); // Date of last cleared transaction QCOMPARE(m->clearedBalance("A000002", QDate(2002, 2, 1)), MyMoneyMoney(-1000, 100)); // Date of last transaction QCOMPARE(m->balance("A000002", QDate(2002, 2, 4)), MyMoneyMoney(-3000, 100)); // Date before first transaction QVERIFY(m->clearedBalance("A000002", QDate(2002, 1, 15)).isZero()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } void MyMoneyFileTest::testAdjustedValues() { // create a checking account, an expeense, an investment account and a stock AddOneAccount(); MyMoneyAccount exp1; exp1.setAccountType(eMyMoney::Account::Type::Expense); exp1.setName("Expense1"); exp1.setCurrencyId("EUR"); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->expense(); m->addAccount(exp1, parent); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } testAddEquityAccount(); testBaseCurrency(); MyMoneySecurity stockSecurity(QLatin1String("Blubber"), QLatin1String("TestsockSecurity"), QLatin1String("BLUB"), 1000, 1000, 1000); stockSecurity.setTradingCurrency(QLatin1String("BLUB")); // add the security ft.restart(); try { m->addSecurity(stockSecurity); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } MyMoneyAccount i = m->accountByName("Investment"); MyMoneyAccount stock; ft.restart(); try { stock.setName("Teststock"); stock.setCurrencyId(stockSecurity.id()); stock.setAccountType(eMyMoney::Account::Type::Stock); m->addAccount(stock, i); ft.commit(); } catch (const MyMoneyException &e) { unexpectedException(e); } // values taken from real example on https://bugs.kde.org/show_bug.cgi?id=345655 MyMoneySplit s1, s2, s3; s1.setAccountId(QLatin1String("A000001")); s1.setShares(MyMoneyMoney(QLatin1String("-99901/1000"))); s1.setValue(MyMoneyMoney(QLatin1String("-999/10"))); s2.setAccountId(exp1.id()); s2.setShares(MyMoneyMoney(QLatin1String("-611/250"))); s2.setValue(MyMoneyMoney(QLatin1String("-61/25"))); s3.setAccountId(stock.id()); s3.setAction(eMyMoney::Split::InvestmentTransactionType::BuyShares); s3.setShares(MyMoneyMoney(QLatin1String("64901/100000"))); s3.setPrice(MyMoneyMoney(QLatin1String("157689/1000"))); s3.setValue(MyMoneyMoney(QLatin1String("102340161/1000000"))); MyMoneyTransaction t; t.setCommodity(QLatin1String("EUR")); t.setPostDate(QDate::currentDate()); t.addSplit(s1); t.addSplit(s2); t.addSplit(s3); // make sure the split sum is not zero QVERIFY(!t.splitSum().isZero()); ft.restart(); try { m->addTransaction(t); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } QCOMPARE(t.splitById(s1.id()).shares(), MyMoneyMoney(QLatin1String("-999/10"))); QCOMPARE(t.splitById(s1.id()).value(), MyMoneyMoney(QLatin1String("-999/10"))); QCOMPARE(t.splitById(s2.id()).shares(), MyMoneyMoney(QLatin1String("-61/25"))); QCOMPARE(t.splitById(s2.id()).value(), MyMoneyMoney(QLatin1String("-61/25"))); QCOMPARE(t.splitById(s3.id()).shares(), MyMoneyMoney(QLatin1String("649/1000"))); QCOMPARE(t.splitById(s3.id()).value(), MyMoneyMoney(QLatin1String("10234/100"))); QCOMPARE(t.splitById(s3.id()).price(), MyMoneyMoney(QLatin1String("157689/1000"))); QCOMPARE(t.splitSum(), MyMoneyMoney()); // now reset and check if modify also works s1.setShares(MyMoneyMoney(QLatin1String("-999/10"))); s1.setValue(MyMoneyMoney(QLatin1String("-999/10"))); s2.setShares(MyMoneyMoney(QLatin1String("-61/25"))); s2.setValue(MyMoneyMoney(QLatin1String("-61/25"))); s3.setShares(MyMoneyMoney(QLatin1String("649/1000"))); s3.setPrice(MyMoneyMoney(QLatin1String("157689/1000"))); s3.setValue(MyMoneyMoney(QLatin1String("102340161/1000000"))); t.modifySplit(s1); t.modifySplit(s2); t.modifySplit(s3); // make sure the split sum is not zero QVERIFY(!t.splitSum().isZero()); ft.restart(); try { m->modifyTransaction(t); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // we need to get the transaction from the engine, as modifyTransaction does // not return the modified values MyMoneyTransaction t2 = m->transaction(t.id()); QCOMPARE(t2.splitById(s3.id()).shares(), MyMoneyMoney(QLatin1String("649/1000"))); QCOMPARE(t2.splitById(s3.id()).value(), MyMoneyMoney(QLatin1String("10234/100"))); QCOMPARE(t2.splitById(s3.id()).price(), MyMoneyMoney(QLatin1String("157689/1000"))); QCOMPARE(t2.splitSum(), MyMoneyMoney()); } void MyMoneyFileTest::testVatAssignment() { MyMoneyAccount acc; MyMoneyAccount vat; MyMoneyAccount expense; testAddTransaction(); vat.setName("VAT"); vat.setCurrencyId("EUR"); vat.setAccountType(eMyMoney::Account::Type::Expense); // make it a VAT account vat.setValue(QLatin1String("VatRate"), QLatin1String("20/100")); MyMoneyFileTransaction ft; try { MyMoneyAccount parent = m->expense(); m->addAccount(vat, parent); QVERIFY(!vat.id().isEmpty()); acc = m->account(QLatin1String("A000001")); expense = m->account(QLatin1String("A000003")); QCOMPARE(acc.name(), QLatin1String("Account1")); QCOMPARE(expense.name(), QLatin1String("Expense1")); expense.setValue(QLatin1String("VatAccount"), vat.id()); m->modifyAccount(expense); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // the categories are setup now for gross value entry MyMoneyTransaction tr; MyMoneySplit sp; MyMoneyMoney amount(1707, 100); // setup the transaction sp.setShares(amount); sp.setValue(amount); sp.setAccountId(acc.id()); tr.addSplit(sp); sp.clearId(); sp.setShares(-amount); sp.setValue(-amount); sp.setAccountId(expense.id()); tr.addSplit(sp); QCOMPARE(m->addVATSplit(tr, acc, expense, amount), true); QCOMPARE(tr.splits().count(), 3); QCOMPARE(tr.splitByAccount(acc.id()).shares().toString(), MyMoneyMoney(1707, 100).toString()); QCOMPARE(tr.splitByAccount(expense.id()).shares().toString(), MyMoneyMoney(-1422, 100).toString()); QCOMPARE(tr.splitByAccount(vat.id()).shares().toString(), MyMoneyMoney(-285, 100).toString()); QCOMPARE(tr.splitSum().toString(), MyMoneyMoney().toString()); tr.removeSplits(); ft.restart(); try { expense.setValue(QLatin1String("VatAmount"), QLatin1String("net")); m->modifyAccount(expense); ft.commit(); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } // the categories are setup now for net value entry amount = MyMoneyMoney(1422, 100); sp.clearId(); sp.setShares(amount); sp.setValue(amount); sp.setAccountId(acc.id()); tr.addSplit(sp); sp.clearId(); sp.setShares(-amount); sp.setValue(-amount); sp.setAccountId(expense.id()); tr.addSplit(sp); QCOMPARE(m->addVATSplit(tr, acc, expense, amount), true); QCOMPARE(tr.splits().count(), 3); QCOMPARE(tr.splitByAccount(acc.id()).shares().toString(), MyMoneyMoney(1706, 100).toString()); QCOMPARE(tr.splitByAccount(expense.id()).shares().toString(), MyMoneyMoney(-1422, 100).toString()); QCOMPARE(tr.splitByAccount(vat.id()).shares().toString(), MyMoneyMoney(-284, 100).toString()); QCOMPARE(tr.splitSum().toString(), MyMoneyMoney().toString()); } void MyMoneyFileTest::testEmptyFilter() { testAddTransaction(); try { QList > tList; MyMoneyTransactionFilter filter; MyMoneyFile::instance()->transactionList(tList, filter); QCOMPARE(tList.count(), 2); } catch (const MyMoneyException &) { QFAIL("Unexpected exception!"); } } diff --git a/kmymoney/mymoney/tests/mymoneyfile-test.h b/kmymoney/mymoney/tests/mymoneyfile-test.h index 8f5eb8220..5ed38a5eb 100644 --- a/kmymoney/mymoney/tests/mymoneyfile-test.h +++ b/kmymoney/mymoney/tests/mymoneyfile-test.h @@ -1,130 +1,130 @@ /*************************************************************************** mymoneyfiletest.h ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYFILETEST_H #define MYMONEYFILETEST_H #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyFileTest; #include "mymoneyfile.h" #include "mymoneyaccount.h" -#include "storage/mymoneyseqaccessmgr.h" +#include "storage/mymoneystoragemgr.h" class MyMoneyFileTest : public QObject { Q_OBJECT protected: MyMoneyFile *m; - MyMoneySeqAccessMgr* storage; + MyMoneyStorageMgr* storage; MyMoneyAccount m_inv; private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testEmptyConstructor(); void testAddOneInstitution(); void testAddTwoInstitutions(); void testRemoveInstitution(); void testInstitutionRetrieval(); void testInstitutionListRetrieval(); void testInstitutionModify(); void testSetFunctions(); void testAddAccounts(); void testAddCategories(); void testModifyAccount(); void testModifyStdAccount(); void testReparentAccount(); void testRemoveAccount(); void testRemoveAccountTree(); void testAccountListRetrieval(); void testAddTransaction(); void testIsStandardAccount(); void testHasActiveSplits(); void testModifyTransactionSimple(); void testModifyTransactionNewPostDate(); void testModifyTransactionNewAccount(); void testRemoveTransaction(); void testBalanceTotal(); void testSetAccountName(); void testAddPayee(); void testModifyPayee(); void testRemovePayee(); void testPayeeWithIdentifier(); void testAddTransactionStd(); void testAttachStorage(); void testAccount2Category(); void testCategory2Account(); void testAttachedStorage(); void testHasAccount(); void testAddEquityAccount(); void testReparentEquity(); void testBaseCurrency(); void testOpeningBalanceNoBase(); void testOpeningBalance(); void testAddPrice(); void testRemovePrice(); void testGetPrice(); void testAddAccountMissingCurrency(); void testAddTransactionToClosedAccount(); void testRemoveTransactionFromClosedAccount(); void testModifyTransactionInClosedAccount(); void testStorageId(); void testHasMatchingOnlineBalance_emptyAccountWithoutImportedBalance(); void testHasMatchingOnlineBalance_emptyAccountWithEqualImportedBalance(); void testHasMatchingOnlineBalance_emptyAccountWithUnequalImportedBalance(); void testHasNewerTransaction_withoutAnyTransaction_afterLastImportedTransaction(); void testHasNewerTransaction_withoutNewerTransaction_afterLastImportedTransaction(); void testHasNewerTransaction_withNewerTransaction_afterLastImportedTransaction(); void testCountTransactionsWithSpecificReconciliationState_noTransactions(); void testCountTransactionsWithSpecificReconciliationState_transactionWithWantedReconcileState(); void testCountTransactionsWithSpecificReconciliationState_transactionWithUnwantedReconcileState(); void testAddOnlineJob(); void testGetOnlineJob(); void testRemoveOnlineJob(); void testRemoveLockedOnlineJob(); void testOnlineJobRollback(); void testModifyOnlineJob(); void testClearedBalance(); void testAdjustedValues(); void testVatAssignment(); void testEmptyFilter(); private Q_SLOTS: void objectAdded(eMyMoney::File::Object type, const MyMoneyObject * const obj); void objectModified(eMyMoney::File::Object type, const MyMoneyObject * const obj); void objectRemoved(eMyMoney::File::Object type, const QString& id); void balanceChanged(const MyMoneyAccount& account); void valueChanged(const MyMoneyAccount& account); private: void testRemoveStdAccount(const MyMoneyAccount& acc); void testReparentEquity(QList& list, MyMoneyAccount& parent); void clearObjectLists(); void AddOneAccount(); private: QStringList m_objectsAdded; QStringList m_objectsModified; QStringList m_objectsRemoved; QStringList m_balanceChanged; QStringList m_valueChanged; }; #endif diff --git a/kmymoney/mymoney/tests/mymoneyforecast-test.cpp b/kmymoney/mymoney/tests/mymoneyforecast-test.cpp index 5a5d442a9..348e7009a 100644 --- a/kmymoney/mymoney/tests/mymoneyforecast-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyforecast-test.cpp @@ -1,982 +1,982 @@ /*************************************************************************** mymoneyforecasttest.cpp ------------------- copyright : (C) 2007 by Alvaro Soliverez email : asoliverez@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyforecast-test.h" #include #include #include #include "mymoneybudget.h" #include "mymoneyexception.h" #include "mymoneystoragedump.h" #include "mymoneystoragexml.h" #include "reportstestcommon.h" #include "mymoneyinstitution.h" #include "mymoneysecurity.h" #include "mymoneymoney.h" #include "mymoneysplit.h" #include "mymoneyschedule.h" #include "mymoneypayee.h" #include "mymoneyenums.h" using namespace eMyMoney; using namespace test; QTEST_GUILESS_MAIN(MyMoneyForecastTest) MyMoneyForecastTest::MyMoneyForecastTest() { this->moT1 = MyMoneyMoney(57, 1); this->moT2 = MyMoneyMoney(63, 1); this->moT3 = MyMoneyMoney(84, 1); this->moT4 = MyMoneyMoney(62, 1); this->moT5 = MyMoneyMoney(104, 1); } void MyMoneyForecastTest::init() { //all this has been taken from pivottabletest.cpp, by Thomas Baumgart and Ace Jones - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Alvaro Soliverez"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount(QString("Checking Account"), Account::Type::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset, "USD"); acCredit = makeAccount(QString("Credit Card"), Account::Type::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability, "USD"); acSolo = makeAccount(QString("Solo"), Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD"); acParent = makeAccount(QString("Parent"), Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD"); acChild = makeAccount(QString("Child"), Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent, "USD"); acForeign = makeAccount(QString("Foreign"), Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense, "USD"); acInvestment = makeAccount("Investment", Account::Type::Investment, moZero, QDate(2004, 1, 1), acAsset, "USD"); acSecondChild = makeAccount(QString("Second Child"), Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent, "USD"); acGrandChild1 = makeAccount(QString("Grand Child 1"), Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild, "USD"); acGrandChild2 = makeAccount(QString("Grand Child 2"), Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild, "USD"); //this account added to have an account to test opening date calculations acCash = makeAccount(QString("Cash"), Account::Type::Cash, moCreditOpen, QDate::currentDate().addDays(-2), acAsset, "USD"); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void MyMoneyForecastTest::cleanup() { file->detachStorage(storage); delete storage; } void MyMoneyForecastTest::testEmptyConstructor() { MyMoneyForecast a; MyMoneyAccount b; QVERIFY(a.forecastBalance(b, QDate::currentDate()).isZero()); QVERIFY(!a.isForecastAccount(b)); QVERIFY(a.forecastBalance(b, QDate::currentDate()) == MyMoneyMoney()); QVERIFY(a.daysToMinimumBalance(b) == -1); QVERIFY(a.daysToZeroBalance(b) == -2); QVERIFY(a.forecastDays() == 90); QVERIFY(a.accountsCycle() == 30); QVERIFY(a.forecastCycles() == 3); QVERIFY(a.historyStartDate() == QDate::currentDate().addDays(-3*30)); QVERIFY(a.historyEndDate() == QDate::currentDate().addDays(-1)); QVERIFY(a.historyDays() == 30 * 3); } void MyMoneyForecastTest::testDoForecastInit() { MyMoneyForecast a; a.doForecast(); /* //check the illegal argument validation try { KMyMoneyGlobalSettings::setForecastDays(-10); a.doForecast(); } catch (const MyMoneyException &e) { QFAIL("Unexpected exception"); } try { KMyMoneyGlobalSettings::setForecastAccountCycle(-20); a.doForecast(); } catch (const MyMoneyException &e) { QFAIL("Unexpected exception"); } try { KMyMoneyGlobalSettings::setForecastCycles(-10); a.doForecast(); } catch (const MyMoneyException &e) { QFAIL("Unexpected exception"); } try { KMyMoneyGlobalSettings::setForecastAccountCycle(0); a.doForecast(); } catch (const MyMoneyException &e) { QFAIL("Unexpected exception"); } try { KMyMoneyGlobalSettings::setForecastDays(0); KMyMoneyGlobalSettings::setForecastCycles(0); KMyMoneyGlobalSettings::setForecastAccountCycle(0); a.doForecast(); } catch (const MyMoneyException &e) { QVERIFY("Unexpected exception"); }*/ } //test that it forecasts correctly with transactions in the period of forecast void MyMoneyForecastTest::testDoForecast() { //set up environment MyMoneyForecast a; MyMoneyAccount a_checking = file->account(acChecking); MyMoneyAccount a_credit = file->account(acCredit); //test empty forecast a.doForecast(); //this is just to check nothing goes wrong if forecast is run agains an empty template //setup some transactions TransactionHelper t1(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acChecking, acSolo); TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -(this->moT2), acCredit, acParent); TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), this->moT1, acCredit, acChecking); a.setForecastMethod(1); a.setForecastDays(3); a.setAccountsCycle(1); a.setForecastCycles(1); a.setBeginForecastDay(0); a.setHistoryMethod(0); //moving average a.doForecast(); //checking didn't have balance variations, so the forecast should be equal to the current balance MyMoneyMoney b_checking = file->balance(a_checking.id(), QDate::currentDate()); QVERIFY(a.forecastBalance(a_checking, QDate::currentDate().addDays(1)) == b_checking); QVERIFY(a.forecastBalance(a_checking, QDate::currentDate().addDays(2)) == b_checking); QVERIFY(a.forecastBalance(a_checking, QDate::currentDate().addDays(3)) == b_checking); QVERIFY(a.forecastBalance(a_checking, QDate::currentDate()) == b_checking); //credit had a variation so the forecast should be different for each day MyMoneyMoney b_credit = file->balance(a_credit.id(), QDate::currentDate()); QVERIFY(a.forecastBalance(a_credit, 0) == b_credit); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == (b_credit + (moT2 - moT1))); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(2)) == (b_credit + ((moT2 - moT1)*2))); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(3)) == b_credit + ((moT2 - moT1)*3)); a.setHistoryMethod(1); //weighted moving average a.doForecast(); QVERIFY(a.forecastBalance(a_credit, 0) == b_credit); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == (b_credit + (moT2 - moT1))); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(2)) == (b_credit + ((moT2 - moT1)*2))); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(3)) == b_credit + ((moT2 - moT1)*3)); //insert transactions outside the forecast period. The calculation should be the same. TransactionHelper t4(QDate::currentDate().addDays(-2), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT2, acCredit, acParent); TransactionHelper t5(QDate::currentDate().addDays(-10), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT2, acCredit, acParent); TransactionHelper t6(QDate::currentDate().addDays(-3), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT2, acCredit, acParent); a.setForecastMethod(1); a.setForecastDays(3); a.setAccountsCycle(1); a.setForecastCycles(1); a.setBeginForecastDay(0); a.setHistoryMethod(0); //moving average a.doForecast(); //check forecast b_credit = file->balance(a_credit.id(), QDate::currentDate()); MyMoneyMoney b_credit_1_exp = (b_credit + ((moT2 - moT1))); MyMoneyMoney b_credit_2 = a.forecastBalance(a_credit, QDate::currentDate().addDays(2)); MyMoneyMoney b_credit_2_exp = (b_credit + ((moT2 - moT1) * 2)); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate()) == file->balance(a_credit.id(), QDate::currentDate())); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == b_credit + (moT2 - moT1)); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(2)) == b_credit + ((moT2 - moT1)*2)); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(3)) == b_credit + ((moT2 - moT1)*3)); //test weighted moving average a.setForecastMethod(1); a.setForecastDays(3); a.setAccountsCycle(1); a.setForecastCycles(3); a.setBeginForecastDay(0); a.setHistoryMethod(1); a.doForecast(); QVERIFY(a.forecastBalance(a_credit, 0) == b_credit); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == (b_credit + (((moT2 - moT1)*3 + moT2*2 + moT2) / MyMoneyMoney(6, 1)))); } void MyMoneyForecastTest::testGetForecastAccountList() { MyMoneyForecast a; MyMoneyAccount a_checking = file->account(acChecking); MyMoneyAccount a_parent = file->account(acParent); QList b; b = a.forecastAccountList(); //check that it contains asset account, but not expense accounts QVERIFY(b.contains(a_checking)); QVERIFY(!b.contains(a_parent)); } void MyMoneyForecastTest::testCalculateAccountTrend() { //set up environment TransactionHelper t1(QDate::currentDate().addDays(-3), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT2, acChecking, acSolo); MyMoneyAccount a_checking = file->account(acChecking); //test invalid arguments try { MyMoneyForecast::calculateAccountTrend(a_checking, 0); } catch (const MyMoneyException &e) { QVERIFY(e.what().compare("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0") == 0); } try { MyMoneyForecast::calculateAccountTrend(a_checking, -10); } catch (const MyMoneyException &e) { QVERIFY(e.what().compare("Illegal arguments when calling calculateAccountTrend. trendDays must be higher than 0") == 0); } //test that it calculates correctly QVERIFY(MyMoneyForecast::calculateAccountTrend(a_checking , 3) == moT2 / MyMoneyMoney(3, 1)); //test that it works for all kind of accounts MyMoneyAccount a_solo = file->account(acSolo); MyMoneyMoney soloTrend = MyMoneyForecast::calculateAccountTrend(a_solo, 3); MyMoneyMoney soloTrendExp = -moT2 / MyMoneyMoney(3, 1); QVERIFY(MyMoneyForecast::calculateAccountTrend(a_solo, 3) == -moT2 / MyMoneyMoney(3, 1)); //test that it does not take into account the transactions of the opening date of the account MyMoneyAccount a_cash = file->account(acCash); TransactionHelper t2(QDate::currentDate().addDays(-2), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), moT2, acCash, acParent); TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), moT1, acCash, acParent); QVERIFY(MyMoneyForecast::calculateAccountTrend(a_cash, 3) == -moT1); } void MyMoneyForecastTest::testGetForecastBalance() { //set up environment MyMoneyForecast a; TransactionHelper t1(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acChecking, acSolo); TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -(this->moT2), acCredit, acParent); TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), this->moT1, acCredit, acChecking); a.setForecastMethod(1); a.setForecastDays(3); a.setAccountsCycle(1); a.setForecastCycles(1); a.setHistoryMethod(0); a.doForecast(); MyMoneyAccount a_checking = file->account(acChecking); MyMoneyAccount a_credit = file->account(acCredit); //test invalid arguments QVERIFY(a.forecastBalance(a_checking, QDate::currentDate().addDays(-1)) == MyMoneyMoney()); QVERIFY(a.forecastBalance(a_checking, QDate::currentDate().addDays(-10)) == MyMoneyMoney()); QVERIFY(a.forecastBalance(a_checking, -1) == MyMoneyMoney()); QVERIFY(a.forecastBalance(a_checking, -100) == MyMoneyMoney()); //test a date outside the forecast days QVERIFY(a.forecastBalance(a_checking, QDate::currentDate().addDays(4)) == MyMoneyMoney()); QVERIFY(a.forecastBalance(a_checking, 4) == MyMoneyMoney()); QVERIFY(a.forecastBalance(a_checking, QDate::currentDate().addDays(10)) == MyMoneyMoney()); QVERIFY(a.forecastBalance(a_checking, 10) == MyMoneyMoney()); //test it returns valid results MyMoneyMoney b_credit = file->balance(a_credit.id(), QDate::currentDate()); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate()) == file->balance(a_credit.id(), QDate::currentDate())); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(1)) == b_credit + (moT2 - moT1)); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(2)) == b_credit + ((moT2 - moT1)*2)); QVERIFY(a.forecastBalance(a_credit, QDate::currentDate().addDays(3)) == b_credit + ((moT2 - moT1)*3)); } void MyMoneyForecastTest::testIsForecastAccount() { MyMoneyForecast a; MyMoneyAccount a_checking = file->account(acChecking); MyMoneyAccount a_solo = file->account(acSolo); MyMoneyAccount a_investment = file->account(acInvestment); //test an invalid account QVERIFY(a.isForecastAccount(a_solo) == false); QVERIFY(a.isForecastAccount(a_investment) == true); //test a valid account QVERIFY(a.isForecastAccount(a_checking) == true); } void MyMoneyForecastTest::testDoFutureScheduledForecast() { //set up future transactions MyMoneyForecast a; MyMoneyAccount a_cash = file->account(acCash); TransactionHelper t1(QDate::currentDate().addDays(1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT1, acCash, acParent); TransactionHelper t2(QDate::currentDate().addDays(2), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT2, acCash, acParent); TransactionHelper t3(QDate::currentDate().addDays(3), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT3, acCash, acParent); TransactionHelper t4(QDate::currentDate().addDays(10), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT4, acCash, acParent); a.setForecastMethod(0); a.setForecastDays(3); a.setAccountsCycle(1); a.setForecastCycles(1); a.doForecast(); MyMoneyMoney b_cash = file->balance(a_cash.id(), QDate::currentDate()); //test valid results QVERIFY(a.forecastBalance(a_cash, QDate::currentDate()) == b_cash); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(1)) == b_cash + moT1); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(2)) == b_cash + moT1 + moT2); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(3)) == b_cash + moT1 + moT2 + moT3); } void MyMoneyForecastTest::testScheduleForecast() { //set up schedule environment for testing MyMoneyAccount a_cash = file->account(acCash); MyMoneyAccount a_parent = file->account(acParent); MyMoneyFileTransaction ft; MyMoneySchedule sch("A Name", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::DirectDebit, QDate::currentDate().addDays(1), QDate(), true, true); MyMoneyTransaction t; t.setPostDate(QDate::currentDate().addDays(1)); t.setEntryDate(QDate::currentDate().addDays(1)); //t.setId("T000000000000000001"); t.setBankID("BID"); t.setMemo("Wohnung:Miete"); t.setCommodity("USD"); t.setValue("key", "value"); MyMoneySplit s; s.setPayeeId("P000001"); s.setShares(moT2); s.setValue(moT2); s.setAccountId(a_parent.id()); s.setBankID("SPID1"); s.setReconcileFlag(eMyMoney::Split::State::Reconciled); t.addSplit(s); s.setPayeeId("P000001"); s.setShares(-moT2); s.setValue(-moT2); s.setAccountId(a_cash.id()); s.setBankID("SPID2"); s.setReconcileFlag(eMyMoney::Split::State::Cleared); s.clearId(); t.addSplit(s); sch.setTransaction(t); file->addSchedule(sch); ft.commit(); MyMoneyFileTransaction ft3; MyMoneySchedule sch3("A Name1", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::DirectDebit, QDate::currentDate().addDays(5), QDate(), true, true); //sch.setLastPayment(QDate::currentDate()); //sch.recordPayment(QDate::currentDate().addDays(1)); //sch.setId("SCH0001"); MyMoneyTransaction t3; t3.setPostDate(QDate::currentDate().addDays(5)); t3.setEntryDate(QDate::currentDate().addDays(5)); //t.setId("T000000000000000001"); t3.setBankID("BID"); t3.setMemo("Wohnung:Miete"); t3.setCommodity("USD"); t3.setValue("key", "value"); MyMoneySplit s3; s3.setPayeeId("P000001"); s3.setShares(moT2); s3.setValue(moT2); s3.setAccountId(a_parent.id()); s3.setBankID("SPID1"); s3.setReconcileFlag(eMyMoney::Split::State::Reconciled); t3.addSplit(s3); s3.setPayeeId("P000001"); s3.setShares(-moT2); s3.setValue(-moT2); s3.setAccountId(a_cash.id()); s3.setBankID("SPID2"); s3.setReconcileFlag(eMyMoney::Split::State::Cleared); s3.clearId(); t3.addSplit(s3); sch3.setTransaction(t3); file->addSchedule(sch3); ft3.commit(); MyMoneyFileTransaction ft2; MyMoneySchedule sch2("A Name2", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::DirectDebit, QDate::currentDate().addDays(2), QDate(), true, true); //sch.setLastPayment(QDate::currentDate()); //sch.recordPayment(QDate::currentDate().addDays(1)); //sch.setId("SCH0001"); MyMoneyTransaction t2; t2.setPostDate(QDate::currentDate().addDays(2)); t2.setEntryDate(QDate::currentDate().addDays(2)); //t.setId("T000000000000000001"); t2.setBankID("BID"); t2.setMemo("Wohnung:Miete"); t2.setCommodity("USD"); t2.setValue("key", "value"); MyMoneySplit s2; s2.setPayeeId("P000001"); s2.setShares(moT1); s2.setValue(moT1); s2.setAccountId(a_parent.id()); s2.setBankID("SPID1"); s2.setReconcileFlag(eMyMoney::Split::State::Reconciled); t2.addSplit(s2); s2.setPayeeId("P000001"); s2.setShares(-moT1); s2.setValue(-moT1); s2.setAccountId(a_cash.id()); s2.setBankID("SPID2"); s2.setReconcileFlag(eMyMoney::Split::State::Cleared); s2.clearId(); t2.addSplit(s2); sch2.setTransaction(t2); file->addSchedule(sch2); ft2.commit(); //run forecast MyMoneyForecast a; a.setForecastMethod(0); a.setForecastDays(3); a.setAccountsCycle(1); a.setForecastCycles(1); a.doForecast(); //check result for single schedule MyMoneyMoney b_cash = file->balance(a_cash.id(), QDate::currentDate()); MyMoneyMoney b_cash1 = a.forecastBalance(a_cash, QDate::currentDate().addDays(1)); //test valid results QVERIFY(a.forecastBalance(a_cash, QDate::currentDate()) == b_cash); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(1)) == b_cash - moT2); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(2)) == b_cash - moT2 - moT1); } void MyMoneyForecastTest::testDaysToMinimumBalance() { //setup environment MyMoneyForecast a; MyMoneyAccount a_cash = file->account(acCash); MyMoneyAccount a_credit = file->account(acCredit); MyMoneyAccount a_parent = file->account(acParent); a_cash.setValue("minBalanceAbsolute", "50"); a_credit.setValue("minBalanceAbsolute", "50"); TransactionHelper t1(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moT1, acCash, acParent); TransactionHelper t2(QDate::currentDate().addDays(2), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), moT2, acCash, acParent); TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), -moT1, acCredit, acParent); TransactionHelper t4(QDate::currentDate().addDays(4), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moT5, acCredit, acParent); a.setForecastMethod(0); a.setForecastDays(3); a.setAccountsCycle(1); a.setForecastCycles(1); a.setBeginForecastDay(0); a.doForecast(); //test invalid arguments MyMoneyAccount nullAcc; QVERIFY(a.daysToMinimumBalance(nullAcc) == -1); //test when not a forecast account QVERIFY(a.daysToMinimumBalance(a_parent) == -1); //test it warns when inside the forecast period QVERIFY(a.daysToMinimumBalance(a_cash) == 2); //test it does not warn when it will be outside of the forecast period QVERIFY(a.daysToMinimumBalance(a_credit) == -1); } void MyMoneyForecastTest::testDaysToZeroBalance() { //set up environment MyMoneyAccount a_Solo = file->account(acSolo); MyMoneyAccount a_Cash = file->account(acCash); MyMoneyAccount a_Credit = file->account(acCredit); //MyMoneyFileTransaction ft; TransactionHelper t1(QDate::currentDate().addDays(2), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), -moT1, acChecking, acSolo); TransactionHelper t2(QDate::currentDate().addDays(2), MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), (moT5), acCash, acCredit); TransactionHelper t3(QDate::currentDate().addDays(2), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), (moT5*100), acCredit, acParent); //ft.commit(); MyMoneyForecast a; a.setForecastMethod(0); a.setForecastDays(30); a.setAccountsCycle(1); a.setForecastCycles(3); a.doForecast(); //test invalid arguments MyMoneyAccount nullAcc; try { a.daysToZeroBalance(nullAcc); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } //test when not a forecast account MyMoneyAccount a_solo = file->account(acSolo); int iSolo = a.daysToZeroBalance(a_Solo); QVERIFY(iSolo == -2); //test it warns when inside the forecast period MyMoneyMoney fCash = a.forecastBalance(a_Cash, QDate::currentDate().addDays(2)); QVERIFY(a.daysToZeroBalance(a_Cash) == 2); //test it does not warn when it will be outside of the forecast period } void MyMoneyForecastTest::testSkipOpeningDate() { //set up environment MyMoneyForecast a; TransactionHelper t1(QDate::currentDate().addDays(-2), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acCash, acSolo); TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT2, acCash, acSolo); a.setForecastMethod(1); a.setForecastDays(3); a.setAccountsCycle(2); a.setForecastCycles(1); a.setHistoryMethod(0); a.doForecast(); MyMoneyAccount a_cash = file->account(acCash); //test it has no variation because it skipped the variation of the opening date MyMoneyMoney b_cash = file->balance(a_cash.id(), QDate::currentDate()); QVERIFY(a.skipOpeningDate() == true); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate()) == b_cash); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(1)) == b_cash); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(2)) == b_cash - moT2); QVERIFY(a.forecastBalance(a_cash, QDate::currentDate().addDays(3)) == b_cash - moT2); } void MyMoneyForecastTest::testAccountMinimumBalanceDateList() { //set up environment MyMoneyForecast a; TransactionHelper t1(QDate::currentDate().addDays(-2), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acCash, acSolo); TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT2, acCash, acSolo); a.setForecastMethod(1); a.setForecastDays(6); a.setAccountsCycle(2); a.setForecastCycles(3); a.setHistoryMethod(0); a.setBeginForecastDay(QDate::currentDate().addDays(1).day()); a.doForecast(); MyMoneyAccount a_cash = file->account(acCash); //test QList dateList; dateList = a.accountMinimumBalanceDateList(a_cash); QList::iterator it = dateList.begin(); QDate minDate = *it; QVERIFY(minDate == QDate::currentDate().addDays(2)); it++; minDate = *it; QVERIFY(minDate == QDate::currentDate().addDays(4)); it++; minDate = *it; QVERIFY(minDate == QDate::currentDate().addDays(6)); } void MyMoneyForecastTest::testAccountMaximumBalanceDateList() { //set up environment MyMoneyForecast a; TransactionHelper t1(QDate::currentDate().addDays(-2), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acCash, acSolo); TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT2, acCash, acSolo); a.setForecastMethod(1); a.setForecastDays(6); a.setAccountsCycle(2); a.setForecastCycles(3); a.setHistoryMethod(0); a.setBeginForecastDay(QDate::currentDate().addDays(1).day()); a.doForecast(); MyMoneyAccount a_cash = file->account(acCash); //test QList dateList; dateList = a.accountMaximumBalanceDateList(a_cash); QList::iterator it = dateList.begin(); QDate maxDate = *it; QVERIFY(maxDate == QDate::currentDate().addDays(1)); it++; maxDate = *it; QVERIFY(maxDate == QDate::currentDate().addDays(3)); it++; maxDate = *it; QVERIFY(maxDate == QDate::currentDate().addDays(5)); } void MyMoneyForecastTest::testAccountAverageBalance() { //set up environment MyMoneyForecast a; TransactionHelper t1(QDate::currentDate().addDays(-2), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acCash, acSolo); TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT2, acCash, acSolo); a.setForecastMethod(1); a.setForecastDays(3); a.setAccountsCycle(2); a.setForecastCycles(1); a.setBeginForecastDay(0); a.doForecast(); MyMoneyAccount a_cash = file->account(acCash); //test MyMoneyMoney b_cash1 = a.forecastBalance(a_cash, QDate::currentDate().addDays(1)); MyMoneyMoney b_cash2 = a.forecastBalance(a_cash, QDate::currentDate().addDays(2)); MyMoneyMoney b_cash3 = a.forecastBalance(a_cash, QDate::currentDate().addDays(3)); MyMoneyMoney average = (b_cash1 + b_cash2 + b_cash3) / MyMoneyMoney(3, 1); QVERIFY(a.accountAverageBalance(a_cash) == average); } void MyMoneyForecastTest::testBeginForecastDate() { //set up environment MyMoneyForecast a; QDate beginDate; int beginDay; a.setForecastMethod(1); a.setForecastDays(90); a.setAccountsCycle(14); a.setForecastCycles(3); a.setBeginForecastDay(0); a.doForecast(); //test when using old method without begin day QVERIFY(QDate::currentDate() == a.beginForecastDate()); //setup begin to last day of month a.setBeginForecastDay(31); beginDay = a.beginForecastDay(); a.doForecast(); //test if (QDate::currentDate().day() < beginDay) { if (QDate::currentDate().daysInMonth() < beginDay) beginDay = QDate::currentDate().daysInMonth(); beginDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), beginDay); QVERIFY(beginDate == a.beginForecastDate()); } //setup begin day to same date a.setBeginForecastDay(QDate::currentDate().day()); beginDay = a.beginForecastDay(); a.doForecast(); QVERIFY(QDate::currentDate() == a.beginForecastDate()); //setup to first day of month with small interval a.setBeginForecastDay(1); a.setAccountsCycle(1); beginDay = a.beginForecastDay(); a.doForecast(); //test if (QDate::currentDate() == a.beginForecastDate()) { QVERIFY(QDate::currentDate() == a.beginForecastDate()); } else { beginDay = ((((QDate::currentDate().day() - beginDay) / a.accountsCycle()) + 1) * a.accountsCycle()) + beginDay; if (beginDay > QDate::currentDate().daysInMonth()) beginDay = QDate::currentDate().daysInMonth(); beginDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), beginDay); if (QDate::currentDate().day() == QDate::currentDate().daysInMonth()) { std::cout << std::endl << "testBeginForecastDate(): test of first day of month with small interval skipped because it is the last day of month" << std::endl; } else { QVERIFY(beginDate == a.beginForecastDate()); } } //setup to test when current date plus cycle equals begin day a.setAccountsCycle(14); beginDay = QDate::currentDate().addDays(14).day(); a.setBeginForecastDay(beginDay); beginDate = QDate::currentDate().addDays(14); a.doForecast(); //test QVERIFY(beginDate == a.beginForecastDate()); //setup to test when the begin day will be next month a.setBeginForecastDay(1); a.setAccountsCycle(40); a.doForecast(); beginDate = QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1); //test if (QDate::currentDate().day() > 1) { QVERIFY(beginDate == a.beginForecastDate()); } else { //test is not valid if today is 1st of month std::cout << std::endl << "testBeginForecastDate(): test of first day of month skipped because current day is 1st of month" << std::endl; } } void MyMoneyForecastTest::testHistoryDays() { MyMoneyForecast a; QVERIFY(a.historyStartDate() == QDate::currentDate().addDays(-a.forecastCycles()*a.accountsCycle())); QVERIFY(a.historyEndDate() == QDate::currentDate().addDays(-1)); QVERIFY(a.historyDays() == a.forecastCycles()*a.accountsCycle()); a.setForecastMethod(1); a.setForecastDays(90); a.setAccountsCycle(14); a.setForecastCycles(3); a.setBeginForecastDay(0); a.doForecast(); QVERIFY(a.historyStartDate() == QDate::currentDate().addDays(-14*3)); QVERIFY(a.historyDays() == (14*3)); QVERIFY(a.historyEndDate() == (QDate::currentDate().addDays(-1))); } void MyMoneyForecastTest::testCreateBudget() { //set up environment MyMoneyForecast a; MyMoneyForecast b; MyMoneyBudget budget; TransactionHelper t1(QDate(2005, 1, 3), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acCash, acSolo); TransactionHelper t2(QDate(2005, 1, 15), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT2, acCash, acParent); TransactionHelper t3(QDate(2005, 1, 30), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT3, acCash, acSolo); TransactionHelper t4(QDate(2006, 1, 25), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT4, acCash, acParent); TransactionHelper t5(QDate(2005, 4, 3), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acCash, acSolo); TransactionHelper t6(QDate(2006, 5, 15), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT2, acCash, acParent); TransactionHelper t7(QDate(2005, 8, 3), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT3, acCash, acSolo); TransactionHelper t8(QDate(2006, 9, 15), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT4, acCash, acParent); a.setHistoryMethod(0); a.setForecastMethod(1); a.createBudget(budget, QDate(2005, 1, 1), QDate(2006, 12, 31), QDate(2007, 1, 1), QDate(2007, 12, 31), true); //test MyMoneyAccount a_solo = file->account(acSolo); MyMoneyAccount a_parent = file->account(acParent); //test it has no variation because it skipped the variation of the opening date QVERIFY(a.forecastBalance(a_solo, QDate(2007, 1, 1)) == ((moT1 + moT3) / MyMoneyMoney(2, 1))); QVERIFY(a.forecastBalance(a_parent, QDate(2007, 1, 1)) == ((moT2 + moT4) / MyMoneyMoney(2, 1))); QVERIFY(a.forecastBalance(a_solo, QDate(2007, 4, 1)) == ((moT1) / MyMoneyMoney(2, 1))); QVERIFY(a.forecastBalance(a_parent, QDate(2007, 5, 1)) == ((moT2) / MyMoneyMoney(2, 1))); QVERIFY(a.forecastBalance(a_solo, QDate(2007, 8, 1)) == ((moT3) / MyMoneyMoney(2, 1))); QVERIFY(a.forecastBalance(a_parent, QDate(2007, 9, 1)) == ((moT4) / MyMoneyMoney(2, 1))); //test the budget object returned by the method QVERIFY(budget.account(a_parent.id()).period(QDate(2007, 9, 1)).amount() == ((moT4) / MyMoneyMoney(2, 1))); //setup test for a length lower than a year b.setForecastMethod(1); b.setHistoryMethod(0); b.createBudget(budget, QDate(2005, 1, 1), QDate(2005, 6, 30), QDate(2007, 1, 1), QDate(2007, 6, 30), true); //test QVERIFY(b.forecastBalance(a_solo, QDate(2007, 1, 1)) == (moT1 + moT3)); QVERIFY(b.forecastBalance(a_parent, QDate(2007, 1, 1)) == (moT2)); QVERIFY(b.forecastBalance(a_solo, QDate(2007, 4, 1)) == (moT1)); QVERIFY(b.forecastBalance(a_parent, QDate(2007, 5, 1)) == (MyMoneyMoney())); //set up schedule environment for testing MyMoneyAccount a_cash = file->account(acCash); MyMoneyFileTransaction ft; MyMoneySchedule sch("A Name", Schedule::Type::Bill, Schedule::Occurrence::Monthly, 1, Schedule::PaymentType::DirectDebit, QDate::currentDate(), QDate(), true, true); MyMoneyTransaction t10; t10.setPostDate(QDate::currentDate().addMonths(1)); t10.setEntryDate(QDate::currentDate().addMonths(1)); //t.setId("T000000000000000001"); t10.setBankID("BID"); t10.setMemo("Wohnung:Miete"); t10.setCommodity("USD"); t10.setValue("key", "value"); MyMoneySplit s; s.setPayeeId("P000001"); s.setShares(moT2); s.setValue(moT2); s.setAccountId(a_parent.id()); s.setBankID("SPID1"); s.setReconcileFlag(eMyMoney::Split::State::Reconciled); t10.addSplit(s); s.setPayeeId("P000001"); s.setShares(-moT2); s.setValue(-moT2); s.setAccountId(a_cash.id()); s.setBankID("SPID2"); s.setReconcileFlag(eMyMoney::Split::State::Cleared); s.clearId(); t10.addSplit(s); sch.setTransaction(t10); file->addSchedule(sch); ft.commit(); //run forecast MyMoneyForecast c; c.setForecastMethod(0); c.setForecastCycles(1); c.createBudget(budget, QDate::currentDate().addYears(-2), QDate::currentDate().addYears(-1), QDate::currentDate().addMonths(-2), QDate::currentDate().addMonths(6), true); MyMoneyMoney c_parent = c.forecastBalance(a_parent, QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1)); //test valid results QVERIFY(c.forecastBalance(a_parent, QDate(QDate::currentDate().addMonths(1).year(), QDate::currentDate().addMonths(1).month(), 1)) == (moT2)); } void MyMoneyForecastTest::testLinearRegression() { //set up environment MyMoneyForecast a; MyMoneyAccount a_checking = file->account(acChecking); MyMoneyAccount a_credit = file->account(acCredit); //setup some transactions TransactionHelper t1(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), this->moT1, acChecking, acSolo); TransactionHelper t2(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -(this->moT2), acCredit, acParent); TransactionHelper t3(QDate::currentDate().addDays(-1), MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), this->moT1, acCredit, acChecking); //TODO Add tests specific for linear regression } diff --git a/kmymoney/mymoney/tests/mymoneyforecast-test.h b/kmymoney/mymoney/tests/mymoneyforecast-test.h index 60ab140e4..e9c7f1faa 100644 --- a/kmymoney/mymoney/tests/mymoneyforecast-test.h +++ b/kmymoney/mymoney/tests/mymoneyforecast-test.h @@ -1,73 +1,73 @@ /*************************************************************************** mymoneyforecasttest.h ------------------- copyright : (C) 2007 by Alvaro Soliverez email : asoliverez@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef MYMONEYFORECASTTEST_H #define MYMONEYFORECASTTEST_H #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyForecastTest; #include "mymoneyfile.h" #include "mymoneymoney.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" #include "mymoneyforecast.h" class MyMoneyForecastTest : public QObject { Q_OBJECT public: MyMoneyForecastTest(); private Q_SLOTS: void init(); void cleanup(); void testEmptyConstructor(); void testDoForecast(); void testDoForecastInit(); void testGetForecastAccountList(); void testCalculateAccountTrend(); void testGetForecastBalance(); void testIsForecastAccount(); void testDoFutureScheduledForecast(); void testDaysToMinimumBalance(); void testDaysToZeroBalance(); void testScheduleForecast(); void testSkipOpeningDate(); void testAccountMinimumBalanceDateList(); void testAccountMaximumBalanceDateList(); void testAccountAverageBalance(); void testBeginForecastDate(); void testHistoryDays(); void testCreateBudget(); void testLinearRegression(); protected: MyMoneyForecast *m; private: - MyMoneySeqAccessMgr* storage; + MyMoneyStorageMgr* storage; MyMoneyFile* file; MyMoneyMoney moT1; MyMoneyMoney moT2; MyMoneyMoney moT3; MyMoneyMoney moT4; MyMoneyMoney moT5; }; #endif diff --git a/kmymoney/mymoney/tests/mymoneyschedule-test.cpp b/kmymoney/mymoney/tests/mymoneyschedule-test.cpp index 949f95929..b4bf19994 100644 --- a/kmymoney/mymoney/tests/mymoneyschedule-test.cpp +++ b/kmymoney/mymoney/tests/mymoneyschedule-test.cpp @@ -1,1779 +1,1779 @@ /*************************************************************************** mymoneyscheduletest.cpp ------------------- copyright : (C) 2002 by Michael Edwardes email : mte@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyschedule-test.h" #include #include #include #include #define KMM_MYMONEY_UNIT_TESTABLE friend class MyMoneyScheduleTest; #include "mymoneysplit.h" #include "mymoneymoney.h" #include "mymoneyschedule.h" #include "mymoneyschedule_p.h" #include "mymoneyfile.h" #include "mymoneyexception.h" #include "mymoneytransaction.h" #include "mymoneytransaction_p.h" -#include "storage/mymoneyseqaccessmgr.h" +#include "storage/mymoneystoragemgr.h" QTEST_GUILESS_MAIN(MyMoneyScheduleTest) using namespace eMyMoney; void MyMoneyScheduleTest::testEmptyConstructor() { MyMoneySchedule s; QCOMPARE(s.id().isEmpty(), true); QCOMPARE(s.occurrence(), Schedule::Occurrence::Any); QCOMPARE(s.type(), Schedule::Type::Any); QCOMPARE(s.paymentType(), Schedule::PaymentType::Any); QCOMPARE(s.isFinished(), false); QCOMPARE(!s.startDate().isValid(), true); QCOMPARE(!s.endDate().isValid(), true); QCOMPARE(!s.lastPayment().isValid(), true); QCOMPARE(s.autoEnter(), false); QCOMPARE(s.name().isEmpty(), true); QCOMPARE(s.willEnd(), false); QCOMPARE(s.lastDayInMonth(), false); } void MyMoneyScheduleTest::testConstructor() { MyMoneySchedule s("A Name", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 1, Schedule::PaymentType::DirectDebit, QDate::currentDate(), QDate(), true, true); QCOMPARE(s.type(), Schedule::Type::Bill); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.paymentType(), Schedule::PaymentType::DirectDebit); QCOMPARE(s.startDate(), QDate()); QCOMPARE(s.willEnd(), false); QCOMPARE(s.isFixed(), true); QCOMPARE(s.autoEnter(), true); QCOMPARE(s.name(), QLatin1String("A Name")); QCOMPARE(!s.endDate().isValid(), true); QCOMPARE(!s.lastPayment().isValid(), true); } void MyMoneyScheduleTest::testSetFunctions() { MyMoneySchedule s; s.d_func()->setId("SCHED001"); QCOMPARE(s.id(), QLatin1String("SCHED001")); s.setType(Schedule::Type::Bill); QCOMPARE(s.type(), Schedule::Type::Bill); s.setEndDate(QDate::currentDate()); QCOMPARE(s.endDate(), QDate::currentDate()); QCOMPARE(s.willEnd(), true); } void MyMoneyScheduleTest::testCopyConstructor() { MyMoneySchedule s; s.d_func()->setId("SCHED001"); s.setType(Schedule::Type::Bill); MyMoneySchedule s2(s); QCOMPARE(s.id(), s2.id()); QCOMPARE(s.type(), s2.type()); } void MyMoneyScheduleTest::testAssignmentConstructor() { MyMoneySchedule s; s.d_func()->setId("SCHED001"); s.setType(Schedule::Type::Bill); MyMoneySchedule s2 = s; QCOMPARE(s.id(), s2.id()); QCOMPARE(s.type(), s2.type()); } void MyMoneyScheduleTest::testOverdue() { MyMoneySchedule sch_overdue; MyMoneySchedule sch_intime; // the following checks only work correctly, if currentDate() is // between the 1st and 27th. If it is between 28th and 31st // we don't perform them. Note: this should be fixed. if (QDate::currentDate().day() > 27 || QDate::currentDate().day() == 1) { qDebug() << "testOverdue() skipped because current day is between 28th and 2nd"; return; } QDate startDate = QDate::currentDate().addDays(-1).addMonths(-23); QDate lastPaymentDate = QDate::currentDate().addDays(-1).addMonths(-1); QString ref = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n"); QString ref_overdue = ref.arg(startDate.toString(Qt::ISODate)) .arg(lastPaymentDate.toString(Qt::ISODate)) .arg(lastPaymentDate.toString(Qt::ISODate)); QString ref_intime = ref.arg(startDate.addDays(1).toString(Qt::ISODate)) .arg(lastPaymentDate.addDays(1).toString(Qt::ISODate)) .arg(lastPaymentDate.addDays(1).toString(Qt::ISODate)); QDomDocument doc; QDomElement node; // std::cout << ref_intime << std::endl; try { doc.setContent(ref_overdue); node = doc.documentElement().firstChild().toElement(); sch_overdue = MyMoneySchedule(node); doc.setContent(ref_intime); node = doc.documentElement().firstChild().toElement(); sch_intime = MyMoneySchedule(node); QCOMPARE(sch_overdue.isOverdue(), true); QCOMPARE(sch_intime.isOverdue(), false); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testNextPayment() /* * Test for a schedule where a payment hasn't yet been made. * First payment is in the future. */ { MyMoneySchedule sch; QString future_sched = QString( "\n" "\n" "\n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(future_sched); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.nextPayment(QDate(2007, 2, 14)), QDate(2007, 2, 17)); QCOMPARE(sch.nextPayment(QDate(2007, 2, 17)), QDate(2008, 2, 17)); QCOMPARE(sch.nextPayment(QDate(2007, 2, 18)), QDate(2008, 2, 17)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testNextPaymentOnLastDayOfMonth() { MyMoneySchedule sch; QString future_sched = QString( "\n" "\n" "\n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(future_sched); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QDate nextPayment; // check for the first payment to happen nextPayment = sch.nextPayment(QDate(2014, 10, 1)); QCOMPARE(nextPayment, QDate(2014, 10, 31)); sch.setLastPayment(nextPayment); QCOMPARE(sch.nextPayment(QDate(2014, 11, 1)), QDate(2014, 11, 30)); QCOMPARE(sch.nextPayment(QDate(2014, 12, 1)), QDate(2014, 12, 31)); QCOMPARE(sch.nextPayment(QDate(2015, 1, 1)), QDate(2015, 1, 31)); QCOMPARE(sch.nextPayment(QDate(2015, 2, 1)), QDate(2015, 2, 28)); QCOMPARE(sch.nextPayment(QDate(2015, 3, 1)), QDate(2015, 3, 31)); // now check that we also cover leap years QCOMPARE(sch.nextPayment(QDate(2016, 2, 1)), QDate(2016, 2, 29)); QCOMPARE(sch.nextPayment(QDate(2016, 3, 1)), QDate(2016, 3, 31)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testAddHalfMonths() { // addHalfMonths is private // Test a Schedule with occurrence EveryHalfMonth using nextPayment MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 1)); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); s.setNextDueDate(s.startDate()); s.setLastPayment(s.startDate()); QString format("yyyy-MM-dd"); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-16")); s.setNextDueDate(QDate(2007, 1, 2)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-17")); s.setNextDueDate(QDate(2007, 1, 3)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-18")); s.setNextDueDate(QDate(2007, 1, 4)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-19")); s.setNextDueDate(QDate(2007, 1, 5)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-20")); s.setNextDueDate(QDate(2007, 1, 6)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-21")); s.setNextDueDate(QDate(2007, 1, 7)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-22")); s.setNextDueDate(QDate(2007, 1, 8)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-23")); s.setNextDueDate(QDate(2007, 1, 9)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-24")); s.setNextDueDate(QDate(2007, 1, 10)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-25")); s.setNextDueDate(QDate(2007, 1, 11)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-26")); s.setNextDueDate(QDate(2007, 1, 12)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-27")); s.setNextDueDate(QDate(2007, 1, 13)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-28")); s.setNextDueDate(QDate(2007, 1, 14)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-29")); // 15 -> Last Day s.setNextDueDate(QDate(2007, 1, 15)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-01-31")); s.setNextDueDate(QDate(2007, 1, 16)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-01")); s.setNextDueDate(QDate(2007, 1, 17)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-02")); s.setNextDueDate(QDate(2007, 1, 18)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-03")); s.setNextDueDate(QDate(2007, 1, 19)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-04")); s.setNextDueDate(QDate(2007, 1, 20)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-05")); s.setNextDueDate(QDate(2007, 1, 21)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-06")); s.setNextDueDate(QDate(2007, 1, 22)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-07")); s.setNextDueDate(QDate(2007, 1, 23)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-08")); s.setNextDueDate(QDate(2007, 1, 24)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-09")); s.setNextDueDate(QDate(2007, 1, 25)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-10")); s.setNextDueDate(QDate(2007, 1, 26)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-11")); s.setNextDueDate(QDate(2007, 1, 27)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-12")); s.setNextDueDate(QDate(2007, 1, 28)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-13")); s.setNextDueDate(QDate(2007, 1, 29)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-14")); // 30th,31st -> 15th s.setNextDueDate(QDate(2007, 1, 30)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-15")); s.setNextDueDate(QDate(2007, 1, 31)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-02-15")); // 30th (last day) s.setNextDueDate(QDate(2007, 4, 30)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2007-05-15")); // 28th of February (Last day): to 15th s.setNextDueDate(QDate(1900, 2, 28)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("1900-03-15")); // 28th of February (Leap year): to 13th s.setNextDueDate(QDate(2000, 2, 28)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2000-03-13")); // 29th of February (Leap year) s.setNextDueDate(QDate(2000, 2, 29)); QCOMPARE(s.nextPayment(s.nextDueDate()).toString(format), QLatin1String("2000-03-15")); // Add multiple transactions s.setStartDate(QDate(2007, 1, 1)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-16")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-01")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-16")); s.setStartDate(QDate(2007, 1, 12)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-27")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-12")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-27")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-12")); s.setStartDate(QDate(2007, 1, 13)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-28")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-13")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-15")); s.setStartDate(QDate(2007, 1, 14)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-29")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-14")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-15")); s.setStartDate(QDate(2007, 1, 15)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-01-31")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-15")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-15")); s.setStartDate(QDate(2007, 1, 16)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-01")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-16")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-01")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-16")); s.setStartDate(QDate(2007, 1, 27)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-12")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-27")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-12")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-27")); s.setStartDate(QDate(2007, 1, 28)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-13")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 1, 29)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-14")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 1, 30)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-15")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 1, 31)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-02-15")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-02-28")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-03-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-03-31")); s.setStartDate(QDate(2007, 4, 29)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-05-14")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-05-29")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-06-14")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-06-29")); s.setStartDate(QDate(2007, 4, 30)); QCOMPARE(s.dateAfter(2).toString(format), QLatin1String("2007-05-15")); QCOMPARE(s.dateAfter(3).toString(format), QLatin1String("2007-05-31")); QCOMPARE(s.dateAfter(4).toString(format), QLatin1String("2007-06-15")); QCOMPARE(s.dateAfter(5).toString(format), QLatin1String("2007-06-30")); } void MyMoneyScheduleTest::testPaymentDates() { MyMoneySchedule sch; QString ref_ok = QString( "\n" "\n" "\n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" "\n" ); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); QDate startDate(2006, 1, 28); QDate endDate(2006, 5, 30); try { sch = MyMoneySchedule(node); QDate nextPayment = sch.nextPayment(startDate); QList list = sch.paymentDates(nextPayment, endDate); QCOMPARE(list.count(), 3); QCOMPARE(list[0], QDate(2006, 2, 28)); QCOMPARE(list[1], QDate(2006, 3, 31)); // Would fall on a Sunday so gets moved back to 28th. QCOMPARE(list[2], QDate(2006, 4, 28)); // Add tests for each possible occurrence. // Check how paymentDates is meant to work // Build a list of expected dates and compare // Schedule::Occurrence::Once sch.setOccurrence(Schedule::Occurrence::Once); startDate.setDate(2009, 1, 1); endDate.setDate(2009, 12, 31); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 1); QCOMPARE(list[0], QDate(2009, 1, 1)); // Schedule::Occurrence::Daily sch.setOccurrence(Schedule::Occurrence::Daily); startDate.setDate(2009, 1, 1); endDate.setDate(2009, 1, 5); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 1, 1)); QCOMPARE(list[1], QDate(2009, 1, 2)); // Would fall on Saturday so gets moved to 2nd. QCOMPARE(list[2], QDate(2009, 1, 2)); // Would fall on Sunday so gets moved to 2nd. QCOMPARE(list[3], QDate(2009, 1, 2)); QCOMPARE(list[4], QDate(2009, 1, 5)); // Schedule::Occurrence::Daily with multiplier 2 sch.setOccurrenceMultiplier(2); list = sch.paymentDates(startDate.addDays(1), endDate); QCOMPARE(list.count(), 2); // Would fall on Sunday so gets moved to 2nd. QCOMPARE(list[0], QDate(2009, 1, 2)); QCOMPARE(list[1], QDate(2009, 1, 5)); sch.setOccurrenceMultiplier(1); // Schedule::Occurrence::Weekly sch.setOccurrence(Schedule::Occurrence::Weekly); startDate.setDate(2009, 1, 6); endDate.setDate(2009, 2, 4); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 1, 6)); QCOMPARE(list[1], QDate(2009, 1, 13)); QCOMPARE(list[2], QDate(2009, 1, 20)); QCOMPARE(list[3], QDate(2009, 1, 27)); QCOMPARE(list[4], QDate(2009, 2, 3)); // Schedule::Occurrence::EveryOtherWeek sch.setOccurrence(Schedule::Occurrence::EveryOtherWeek); startDate.setDate(2009, 2, 5); endDate.setDate(2009, 4, 3); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 2, 5)); QCOMPARE(list[1], QDate(2009, 2, 19)); QCOMPARE(list[2], QDate(2009, 3, 5)); QCOMPARE(list[3], QDate(2009, 3, 19)); QCOMPARE(list[4], QDate(2009, 4, 2)); // Schedule::Occurrence::Fortnightly sch.setOccurrence(Schedule::Occurrence::Fortnightly); startDate.setDate(2009, 4, 4); endDate.setDate(2009, 5, 31); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 4); // First one would fall on a Saturday and would get moved // to 3rd which is before start date so is not in list. // Would fall on a Saturday so gets moved to 17th. QCOMPARE(list[0], QDate(2009, 4, 17)); // Would fall on a Saturday so gets moved to 1st. QCOMPARE(list[1], QDate(2009, 5, 1)); // Would fall on a Saturday so gets moved to 15th. QCOMPARE(list[2], QDate(2009, 5, 15)); // Would fall on a Saturday so gets moved to 29th. QCOMPARE(list[3], QDate(2009, 5, 29)); // Schedule::Occurrence::EveryHalfMonth sch.setOccurrence(Schedule::Occurrence::EveryHalfMonth); startDate.setDate(2009, 6, 1); endDate.setDate(2009, 8, 11); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 6, 1)); QCOMPARE(list[1], QDate(2009, 6, 16)); QCOMPARE(list[2], QDate(2009, 7, 1)); QCOMPARE(list[3], QDate(2009, 7, 16)); // Would fall on a Saturday so gets moved to 31st. QCOMPARE(list[4], QDate(2009, 7, 31)); // Schedule::Occurrence::EveryThreeWeeks sch.setOccurrence(Schedule::Occurrence::EveryThreeWeeks); startDate.setDate(2009, 8, 12); endDate.setDate(2009, 11, 12); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 8, 12)); QCOMPARE(list[1], QDate(2009, 9, 2)); QCOMPARE(list[2], QDate(2009, 9, 23)); QCOMPARE(list[3], QDate(2009, 10, 14)); QCOMPARE(list[4], QDate(2009, 11, 4)); // Schedule::Occurrence::EveryFourWeeks sch.setOccurrence(Schedule::Occurrence::EveryFourWeeks); startDate.setDate(2009, 11, 13); endDate.setDate(2010, 3, 13); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2009, 11, 13)); QCOMPARE(list[1], QDate(2009, 12, 11)); QCOMPARE(list[2], QDate(2010, 1, 8)); QCOMPARE(list[3], QDate(2010, 2, 5)); QCOMPARE(list[4], QDate(2010, 3, 5)); // Schedule::Occurrence::EveryThirtyDays sch.setOccurrence(Schedule::Occurrence::EveryThirtyDays); startDate.setDate(2010, 3, 19); endDate.setDate(2010, 7, 19); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2010, 3, 19)); // Would fall on a Sunday so gets moved to 16th. QCOMPARE(list[1], QDate(2010, 4, 16)); QCOMPARE(list[2], QDate(2010, 5, 18)); QCOMPARE(list[3], QDate(2010, 6, 17)); // Would fall on a Saturday so gets moved to 16th. QCOMPARE(list[4], QDate(2010, 7, 16)); // Schedule::Occurrence::EveryEightWeeks sch.setOccurrence(Schedule::Occurrence::EveryEightWeeks); startDate.setDate(2010, 7, 26); endDate.setDate(2011, 3, 26); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2010, 7, 26)); QCOMPARE(list[1], QDate(2010, 9, 20)); QCOMPARE(list[2], QDate(2010, 11, 15)); QCOMPARE(list[3], QDate(2011, 1, 10)); QCOMPARE(list[4], QDate(2011, 3, 7)); // Schedule::Occurrence::EveryOtherMonth sch.setOccurrence(Schedule::Occurrence::EveryOtherMonth); startDate.setDate(2011, 3, 14); endDate.setDate(2011, 11, 20); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2011, 3, 14)); // Would fall on a Saturday so gets moved to 13th. QCOMPARE(list[1], QDate(2011, 5, 13)); QCOMPARE(list[2], QDate(2011, 7, 14)); QCOMPARE(list[3], QDate(2011, 9, 14)); QCOMPARE(list[4], QDate(2011, 11, 14)); // Schedule::Occurrence::EveryThreeMonths sch.setOccurrence(Schedule::Occurrence::EveryThreeMonths); startDate.setDate(2011, 11, 15); endDate.setDate(2012, 11, 19); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2011, 11, 15)); QCOMPARE(list[1], QDate(2012, 2, 15)); QCOMPARE(list[2], QDate(2012, 5, 15)); QCOMPARE(list[3], QDate(2012, 8, 15)); QCOMPARE(list[4], QDate(2012, 11, 15)); // Schedule::Occurrence::Quarterly sch.setOccurrence(Schedule::Occurrence::Quarterly); startDate.setDate(2012, 11, 20); endDate.setDate(2013, 11, 23); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2012, 11, 20)); QCOMPARE(list[1], QDate(2013, 2, 20)); QCOMPARE(list[2], QDate(2013, 5, 20)); QCOMPARE(list[3], QDate(2013, 8, 20)); QCOMPARE(list[4], QDate(2013, 11, 20)); // Schedule::Occurrence::EveryFourMonths sch.setOccurrence(Schedule::Occurrence::EveryFourMonths); startDate.setDate(2013, 11, 21); endDate.setDate(2015, 3, 23); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2013, 11, 21)); QCOMPARE(list[1], QDate(2014, 3, 21)); QCOMPARE(list[2], QDate(2014, 7, 21)); QCOMPARE(list[3], QDate(2014, 11, 21)); // Would fall on a Saturday so gets moved to 20th. QCOMPARE(list[4], QDate(2015, 3, 20)); // Schedule::Occurrence::TwiceYearly sch.setOccurrence(Schedule::Occurrence::TwiceYearly); startDate.setDate(2015, 3, 22); endDate.setDate(2017, 3, 29); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 4); // First date would fall on a Sunday which would get moved // to 20th which is before start date so not in list. QCOMPARE(list[0], QDate(2015, 9, 22)); QCOMPARE(list[1], QDate(2016, 3, 22)); QCOMPARE(list[2], QDate(2016, 9, 22)); QCOMPARE(list[3], QDate(2017, 3, 22)); // Schedule::Occurrence::Yearly sch.setOccurrence(Schedule::Occurrence::Yearly); startDate.setDate(2017, 3, 23); endDate.setDate(2021, 3, 29); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2017, 3, 23)); QCOMPARE(list[1], QDate(2018, 3, 23)); // Would fall on a Saturday so gets moved to 22nd. QCOMPARE(list[2], QDate(2019, 3, 22)); QCOMPARE(list[3], QDate(2020, 3, 23)); QCOMPARE(list[4], QDate(2021, 3, 23)); // Schedule::Occurrence::EveryOtherYear sch.setOccurrence(Schedule::Occurrence::EveryOtherYear); startDate.setDate(2021, 3, 24); endDate.setDate(2029, 3, 30); sch.setStartDate(startDate); sch.setNextDueDate(startDate); list = sch.paymentDates(startDate, endDate); QCOMPARE(list.count(), 5); QCOMPARE(list[0], QDate(2021, 3, 24)); QCOMPARE(list[1], QDate(2023, 3, 24)); QCOMPARE(list[2], QDate(2025, 3, 24)); QCOMPARE(list[3], QDate(2027, 3, 24)); // Would fall on a Saturday so gets moved to 23rd. QCOMPARE(list[4], QDate(2029, 3, 23)); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testWriteXML() { MyMoneySchedule sch("A Name", Schedule::Type::Bill, Schedule::Occurrence::Weekly, 123, Schedule::PaymentType::DirectDebit, QDate::currentDate(), QDate(), true, true); sch.setLastPayment(QDate::currentDate()); sch.recordPayment(QDate::currentDate()); sch.d_func()->setId("SCH0001"); MyMoneyTransaction t; t.setPostDate(QDate(2001, 12, 28)); t.setEntryDate(QDate(2003, 9, 29)); t.d_func()->setId("T000000000000000001"); t.setMemo("Wohnung:Miete"); t.setCommodity("EUR"); t.setValue("key", "value"); MyMoneySplit s; s.setPayeeId("P000001"); s.setShares(MyMoneyMoney(96379, 100)); s.setValue(MyMoneyMoney(96379, 100)); s.setAccountId("A000076"); s.setBankID("SPID1"); s.setReconcileFlag(eMyMoney::Split::State::Reconciled); t.addSplit(s); s.setPayeeId("P000001"); s.setShares(MyMoneyMoney(-96379, 100)); s.setValue(MyMoneyMoney(-96379, 100)); s.setAccountId("A000276"); s.setBankID("SPID2"); s.setReconcileFlag(eMyMoney::Split::State::Cleared); s.clearId(); t.addSplit(s); sch.setTransaction(t); QDomDocument doc("TEST"); QDomElement el = doc.createElement("SCHEDULE-CONTAINER"); doc.appendChild(el); sch.writeXML(doc, el); QCOMPARE(doc.doctype().name(), QLatin1String("TEST")); QDomElement scheduleContainer = doc.documentElement(); QVERIFY(scheduleContainer.isElement()); QCOMPARE(scheduleContainer.tagName(), QLatin1String("SCHEDULE-CONTAINER")); QCOMPARE(scheduleContainer.childNodes().size(), 1); QVERIFY(scheduleContainer.childNodes().at(0).isElement()); QDomElement schedule = scheduleContainer.childNodes().at(0).toElement(); QCOMPARE(schedule.tagName(), QLatin1String("SCHEDULED_TX")); QCOMPARE(schedule.attribute("id"), QLatin1String("SCH0001")); QCOMPARE(schedule.attribute("paymentType"), QLatin1String("1")); QCOMPARE(schedule.attribute("autoEnter"), QLatin1String("1")); QCOMPARE(schedule.attribute("occurenceMultiplier"), QLatin1String("123")); // krazy:exclude=spelling QCOMPARE(schedule.attribute("startDate"), QDate::currentDate().toString(Qt::ISODate)); QCOMPARE(schedule.attribute("lastPayment"), QDate::currentDate().toString(Qt::ISODate)); QCOMPARE(schedule.attribute("occurenceMultiplier"), QLatin1String("123")); // krazy:exclude=spelling QCOMPARE(schedule.attribute("occurence"), QLatin1String("4")); // krazy:exclude=spelling QCOMPARE(schedule.attribute("type"), QLatin1String("1")); QCOMPARE(schedule.attribute("name"), QLatin1String("A Name")); QCOMPARE(schedule.attribute("fixed"), QLatin1String("1")); QCOMPARE(schedule.attribute("endDate"), QString()); QCOMPARE(schedule.childNodes().size(), 2); QVERIFY(schedule.childNodes().at(0).isElement()); QDomElement payments = schedule.childNodes().at(0).toElement(); QVERIFY(schedule.childNodes().at(1).isElement()); QDomElement transaction = schedule.childNodes().at(1).toElement(); QCOMPARE(transaction.tagName(), QLatin1String("TRANSACTION")); QCOMPARE(transaction.attribute("id"), QString()); QCOMPARE(transaction.attribute("postdate"), QLatin1String("2001-12-28")); QCOMPARE(transaction.attribute("commodity"), QLatin1String("EUR")); QCOMPARE(transaction.attribute("memo"), QLatin1String("Wohnung:Miete")); QCOMPARE(transaction.attribute("entrydate"), QLatin1String("2003-09-29")); QCOMPARE(transaction.childNodes().size(), 2); QVERIFY(transaction.childNodes().at(0).isElement()); QDomElement splits = transaction.childNodes().at(0).toElement(); QCOMPARE(splits.tagName(), QLatin1String("SPLITS")); QCOMPARE(splits.childNodes().size(), 2); QVERIFY(splits.childNodes().at(0).isElement()); QDomElement split1 = splits.childNodes().at(0).toElement(); QCOMPARE(split1.tagName(), QLatin1String("SPLIT")); QCOMPARE(split1.attribute("id"), QLatin1String("S0001")); QCOMPARE(split1.attribute("payee"), QLatin1String("P000001")); QCOMPARE(split1.attribute("reconcileflag"), QLatin1String("2")); QCOMPARE(split1.attribute("shares"), QLatin1String("96379/100")); QCOMPARE(split1.attribute("reconciledate"), QString()); QCOMPARE(split1.attribute("action"), QString()); QCOMPARE(split1.attribute("bankid"), QString()); QCOMPARE(split1.attribute("account"), QLatin1String("A000076")); QCOMPARE(split1.attribute("number"), QString()); QCOMPARE(split1.attribute("value"), QLatin1String("96379/100")); QCOMPARE(split1.attribute("memo"), QString()); QCOMPARE(split1.childNodes().size(), 0); QVERIFY(splits.childNodes().at(1).isElement()); QDomElement split2 = splits.childNodes().at(1).toElement(); QCOMPARE(split2.tagName(), QLatin1String("SPLIT")); QCOMPARE(split2.attribute("id"), QLatin1String("S0002")); QCOMPARE(split2.attribute("payee"), QLatin1String("P000001")); QCOMPARE(split2.attribute("reconcileflag"), QLatin1String("1")); QCOMPARE(split2.attribute("shares"), QLatin1String("-96379/100")); QCOMPARE(split2.attribute("reconciledate"), QString()); QCOMPARE(split2.attribute("action"), QString()); QCOMPARE(split2.attribute("bankid"), QString()); QCOMPARE(split2.attribute("account"), QLatin1String("A000276")); QCOMPARE(split2.attribute("number"), QString()); QCOMPARE(split2.attribute("value"), QLatin1String("-96379/100")); QCOMPARE(split2.attribute("memo"), QString()); QCOMPARE(split2.childNodes().size(), 0); QDomElement keyValuePairs = transaction.childNodes().at(1).toElement(); QCOMPARE(keyValuePairs.tagName(), QLatin1String("KEYVALUEPAIRS")); QCOMPARE(keyValuePairs.childNodes().size(), 1); QVERIFY(keyValuePairs.childNodes().at(0).isElement()); QDomElement keyValuePair1 = keyValuePairs.childNodes().at(0).toElement(); QCOMPARE(keyValuePair1.tagName(), QLatin1String("PAIR")); QCOMPARE(keyValuePair1.attribute("key"), QLatin1String("key")); QCOMPARE(keyValuePair1.attribute("value"), QLatin1String("value")); QCOMPARE(keyValuePair1.childNodes().size(), 0); } void MyMoneyScheduleTest::testReadXML() { MyMoneySchedule sch; QString ref_ok1 = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); // diff to ref_ok1 is that we now have an empty entrydate // in the transaction parameters QString ref_ok2 = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); QString ref_false = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_false); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QFAIL("Missing expected exception"); } catch (const MyMoneyException &) { } doc.setContent(ref_ok1); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.id(), QLatin1String("SCH0002")); QCOMPARE(sch.nextDueDate(), QDate::currentDate().addDays(7)); QCOMPARE(sch.startDate(), QDate::currentDate()); QCOMPARE(sch.endDate(), QDate()); QCOMPARE(sch.autoEnter(), true); QCOMPARE(sch.isFixed(), true); QCOMPARE(sch.weekendOption(), Schedule::WeekendOption::MoveNothing); QCOMPARE(sch.lastPayment(), QDate::currentDate()); QCOMPARE(sch.paymentType(), Schedule::PaymentType::DirectDebit); QCOMPARE(sch.type(), Schedule::Type::Bill); QCOMPARE(sch.name(), QLatin1String("A Name")); QCOMPARE(sch.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(sch.occurrenceMultiplier(), 1); QCOMPARE(sch.nextDueDate(), sch.lastPayment().addDays(7)); QCOMPARE(sch.recordedPayments().count(), 1); QCOMPARE(sch.recordedPayments()[0], QDate::currentDate()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } doc.setContent(ref_ok2); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.id(), QLatin1String("SCH0002")); QCOMPARE(sch.nextDueDate(), QDate::currentDate().addDays(7)); QCOMPARE(sch.startDate(), QDate::currentDate()); QCOMPARE(sch.endDate(), QDate()); QCOMPARE(sch.autoEnter(), true); QCOMPARE(sch.isFixed(), true); QCOMPARE(sch.weekendOption(), Schedule::WeekendOption::MoveNothing); QCOMPARE(sch.lastPayment(), QDate::currentDate()); QCOMPARE(sch.paymentType(), Schedule::PaymentType::DirectDebit); QCOMPARE(sch.type(), Schedule::Type::Bill); QCOMPARE(sch.name(), QLatin1String("A Name")); QCOMPARE(sch.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(sch.occurrenceMultiplier(), 1); QCOMPARE(sch.nextDueDate(), sch.lastPayment().addDays(7)); QCOMPARE(sch.recordedPayments().count(), 1); QCOMPARE(sch.recordedPayments()[0], QDate::currentDate()); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testHasReferenceTo() { MyMoneySchedule sch; QString ref_ok = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)) .arg(QDate::currentDate().toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } QCOMPARE(sch.hasReferenceTo(QLatin1String("P000001")), true); QCOMPARE(sch.hasReferenceTo(QLatin1String("A000276")), true); QCOMPARE(sch.hasReferenceTo(QLatin1String("A000076")), true); QCOMPARE(sch.hasReferenceTo(QLatin1String("EUR")), true); } void MyMoneyScheduleTest::testAdjustedNextDueDate() { MyMoneySchedule s; QDate dueDate(2007, 9, 3); // start on a Monday for (int i = 0; i < 7; ++i) { s.setNextDueDate(dueDate); s.setWeekendOption(Schedule::WeekendOption::MoveNothing); QCOMPARE(s.adjustedNextDueDate(), dueDate); s.setWeekendOption(Schedule::WeekendOption::MoveBefore); switch (i) { case 5: // Saturday case 6: // Sunday QCOMPARE(s.adjustedNextDueDate(), QDate(2007, 9, 7)); break; default: QCOMPARE(s.adjustedNextDueDate(), dueDate); break; } s.setWeekendOption(Schedule::WeekendOption::MoveAfter); switch (i) { case 5: // Saturday case 6: // Sunday QCOMPARE(s.adjustedNextDueDate(), QDate(2007, 9, 10)); break; default: QCOMPARE(s.adjustedNextDueDate(), dueDate); break; } dueDate = dueDate.addDays(1); } } void MyMoneyScheduleTest::testModifyNextDueDate() { MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 2)); s.setOccurrence(Schedule::Occurrence::Monthly); s.setNextDueDate(s.startDate().addMonths(1)); s.setLastPayment(s.startDate()); QList dates; dates = s.paymentDates(QDate(2007, 2, 2), QDate(2007, 2, 2)); QCOMPARE(s.nextDueDate(), QDate(2007, 2, 2)); QCOMPARE(dates.count(), 1); QCOMPARE(dates[0], QDate(2007, 2, 2)); s.setNextDueDate(QDate(2007, 1, 24)); dates = s.paymentDates(QDate(2007, 2, 1), QDate(2007, 2, 1)); QCOMPARE(s.nextDueDate(), QDate(2007, 1, 24)); QCOMPARE(dates.count(), 0); dates = s.paymentDates(QDate(2007, 1, 24), QDate(2007, 1, 24)); QCOMPARE(dates.count(), 1); dates = s.paymentDates(QDate(2007, 1, 24), QDate(2007, 2, 24)); QCOMPARE(dates.count(), 2); QCOMPARE(dates[0], QDate(2007, 1, 24)); QCOMPARE(dates[1], QDate(2007, 2, 2)); } void MyMoneyScheduleTest::testDaysBetweenEvents() { QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Once), 0); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Daily), 1); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Weekly), 7); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryOtherWeek), 14); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Fortnightly), 14); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryHalfMonth), 15); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryThreeWeeks), 21); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryFourWeeks), 28); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryThirtyDays), 30); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Monthly), 30); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryEightWeeks), 56); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryOtherMonth), 60); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryThreeMonths), 90); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Quarterly), 90); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryFourMonths), 120); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::TwiceYearly), 180); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::Yearly), 360); QCOMPARE(MyMoneySchedule::daysBetweenEvents(Schedule::Occurrence::EveryOtherYear), 0); } void MyMoneyScheduleTest::testEventsPerYear() { QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Once), 0); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Daily), 365); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Weekly), 52); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryOtherWeek), 26); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Fortnightly), 26); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryHalfMonth), 24); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryThreeWeeks), 17); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryFourWeeks), 13); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryThirtyDays), 12); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Monthly), 12); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryEightWeeks), 6); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryOtherMonth), 6); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryThreeMonths), 4); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Quarterly), 4); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryFourMonths), 3); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::TwiceYearly), 2); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::Yearly), 1); QCOMPARE(MyMoneySchedule::eventsPerYear(Schedule::Occurrence::EveryOtherYear), 0); } void MyMoneyScheduleTest::testOccurrenceToString() { // For each occurrenceE test MyMoneySchedule::occurrenceToString(occurrenceE) QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Once), QLatin1String("Once")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Daily), QLatin1String("Daily")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Weekly), QLatin1String("Weekly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherWeek), QLatin1String("Every other week")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Fortnightly), QLatin1String("Fortnightly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryHalfMonth), QLatin1String("Every half month")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeWeeks), QLatin1String("Every three weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourWeeks), QLatin1String("Every four weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThirtyDays), QLatin1String("Every thirty days")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Monthly), QLatin1String("Monthly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryEightWeeks), QLatin1String("Every eight weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherMonth), QLatin1String("Every two months")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeMonths), QLatin1String("Every three months")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Quarterly), QLatin1String("Quarterly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourMonths), QLatin1String("Every four months")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::TwiceYearly), QLatin1String("Twice yearly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Yearly), QLatin1String("Yearly")); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherYear), QLatin1String("Every other year")); // For each occurrenceE set occurrence and compare occurrenceToString() with oTS(occurrence()) MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 1)); s.setNextDueDate(s.startDate()); s.setLastPayment(s.startDate()); s.setOccurrence(Schedule::Occurrence::Once); QCOMPARE(s.occurrenceToString(), QLatin1String("Once")); s.setOccurrence(Schedule::Occurrence::Daily); QCOMPARE(s.occurrenceToString(), QLatin1String("Daily")); s.setOccurrence(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceToString(), QLatin1String("Weekly")); s.setOccurrence(Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week")); // Fortnightly no longer used: Every other week used instead s.setOccurrence(Schedule::Occurrence::Fortnightly); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week")); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrenceToString(), QLatin1String("Every half month")); s.setOccurrence(Schedule::Occurrence::EveryThreeWeeks); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three weeks")); s.setOccurrence(Schedule::Occurrence::EveryFourWeeks); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four weeks")); s.setOccurrence(Schedule::Occurrence::EveryThirtyDays); QCOMPARE(s.occurrenceToString(), QLatin1String("Every thirty days")); s.setOccurrence(Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceToString(), QLatin1String("Monthly")); s.setOccurrence(Schedule::Occurrence::EveryEightWeeks); QCOMPARE(s.occurrenceToString(), QLatin1String("Every eight weeks")); s.setOccurrence(Schedule::Occurrence::EveryOtherMonth); QCOMPARE(s.occurrenceToString(), QLatin1String("Every two months")); s.setOccurrence(Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months")); // Quarterly no longer used. Every three months used instead s.setOccurrence(Schedule::Occurrence::Quarterly); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months")); s.setOccurrence(Schedule::Occurrence::EveryFourMonths); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four months")); s.setOccurrence(Schedule::Occurrence::TwiceYearly); QCOMPARE(s.occurrenceToString(), QLatin1String("Twice yearly")); s.setOccurrence(Schedule::Occurrence::Yearly); QCOMPARE(s.occurrenceToString(), QLatin1String("Yearly")); s.setOccurrence(Schedule::Occurrence::EveryOtherYear); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other year")); // Test occurrenceToString(mult,occ) // Test all pairs equivalent to simple occurrences: should return the same as occurrenceToString(simpleOcc) // TODO replace string with (mult,occ) call. QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Once), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Once)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Daily), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Daily)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Weekly), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherWeek), MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Weekly)); // Fortnightly will no longer be used: only Every Other Week QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryHalfMonth), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::EveryHalfMonth)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeWeeks), MyMoneySchedule::occurrenceToString(3, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourWeeks), MyMoneySchedule::occurrenceToString(4, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Monthly), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryEightWeeks), MyMoneySchedule::occurrenceToString(8, Schedule::Occurrence::Weekly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherMonth), MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryThreeMonths), MyMoneySchedule::occurrenceToString(3, Schedule::Occurrence::Monthly)); // Quarterly will no longer be used: only Every Three Months QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryFourMonths), MyMoneySchedule::occurrenceToString(4, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::TwiceYearly), MyMoneySchedule::occurrenceToString(6, Schedule::Occurrence::Monthly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::Yearly), MyMoneySchedule::occurrenceToString(1, Schedule::Occurrence::Yearly)); QCOMPARE(MyMoneySchedule::occurrenceToString(Schedule::Occurrence::EveryOtherYear), MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Yearly)); // Test additional calls with other mult,occ QCOMPARE(MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Once), QLatin1String("2 times")); QCOMPARE(MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::Daily), QLatin1String("Every 2 days")); QCOMPARE(MyMoneySchedule::occurrenceToString(5, Schedule::Occurrence::Weekly), QLatin1String("Every 5 weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(2, Schedule::Occurrence::EveryHalfMonth), QLatin1String("Every 2 half months")); QCOMPARE(MyMoneySchedule::occurrenceToString(5, Schedule::Occurrence::Monthly), QLatin1String("Every 5 months")); QCOMPARE(MyMoneySchedule::occurrenceToString(3, Schedule::Occurrence::Yearly), QLatin1String("Every 3 years")); QCOMPARE(MyMoneySchedule::occurrenceToString(37, Schedule::Occurrence::Once), QLatin1String("37 times")); QCOMPARE(MyMoneySchedule::occurrenceToString(43, Schedule::Occurrence::Daily), QLatin1String("Every 43 days")); QCOMPARE(MyMoneySchedule::occurrenceToString(61, Schedule::Occurrence::Weekly), QLatin1String("Every 61 weeks")); QCOMPARE(MyMoneySchedule::occurrenceToString(73, Schedule::Occurrence::EveryHalfMonth), QLatin1String("Every 73 half months")); QCOMPARE(MyMoneySchedule::occurrenceToString(83, Schedule::Occurrence::Monthly), QLatin1String("Every 83 months")); QCOMPARE(MyMoneySchedule::occurrenceToString(89, Schedule::Occurrence::Yearly), QLatin1String("Every 89 years")); // Test instance-level occurrenceToString method is using occurrencePeriod and multiplier // For each base occurrence set occurrencePeriod and multiplier s.setOccurrencePeriod(Schedule::Occurrence::Once); s.setOccurrenceMultiplier(1); s.setOccurrence(Schedule::Occurrence::Once); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Once")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("2 times")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("3 times")); s.setOccurrencePeriod(Schedule::Occurrence::Daily); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Daily")); s.setOccurrenceMultiplier(30); QCOMPARE(s.occurrenceToString(), QLatin1String("Every thirty days")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 3 days")); s.setOccurrence(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceToString(), QLatin1String("Weekly")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other week")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three weeks")); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four weeks")); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 5 weeks")); s.setOccurrenceMultiplier(7); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 7 weeks")); s.setOccurrenceMultiplier(8); QCOMPARE(s.occurrenceToString(), QLatin1String("Every eight weeks")); s.setOccurrenceMultiplier(9); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 9 weeks")); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Every half month")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 2 half months")); s.setOccurrence(Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Monthly")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every two months")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every three months")); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceToString(), QLatin1String("Every four months")); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 5 months")); s.setOccurrenceMultiplier(6); QCOMPARE(s.occurrenceToString(), QLatin1String("Twice yearly")); s.setOccurrenceMultiplier(7); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 7 months")); s.setOccurrence(Schedule::Occurrence::Yearly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceToString(), QLatin1String("Yearly")); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceToString(), QLatin1String("Every other year")); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceToString(), QLatin1String("Every 3 years")); } void MyMoneyScheduleTest::testOccurrencePeriodToString() { // For each occurrenceE test MyMoneySchedule::occurrencePeriodToString(occurrenceE) // Base occurrences are translated QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Once), QLatin1String("Once")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Daily), QLatin1String("Day")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Weekly), QLatin1String("Week")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryHalfMonth), QLatin1String("Half-month")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Monthly), QLatin1String("Month")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Yearly), QLatin1String("Year")); // All others are not translated so return Any QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryOtherWeek), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Fortnightly), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryThreeWeeks), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryFourWeeks), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryThirtyDays), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryEightWeeks), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryOtherMonth), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryThreeMonths), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::Quarterly), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryFourMonths), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::TwiceYearly), QLatin1String("Any")); QCOMPARE(MyMoneySchedule::occurrencePeriodToString(Schedule::Occurrence::EveryOtherYear), QLatin1String("Any")); } void MyMoneyScheduleTest::testOccurrencePeriod() { // Each occurrence: // Set occurrence using setOccurrencePeriod // occurrencePeriod should match what we set // occurrence depends on multiplier // TODO: // Once occurrence() and setOccurrence() are converting between compound and simple occurrences // we need to change the occurrence() check and add an occurrenceMultiplier() check MyMoneySchedule s; s.setStartDate(QDate(2007, 1, 1)); s.setNextDueDate(s.startDate()); s.setLastPayment(s.startDate()); // Set all base occurrences s.setOccurrencePeriod(Schedule::Occurrence::Once); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); QCOMPARE(s.occurrence(), Schedule::Occurrence::Once); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); QCOMPARE(s.occurrence(), Schedule::Occurrence::Once); s.setOccurrencePeriod(Schedule::Occurrence::Daily); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily); s.setOccurrenceMultiplier(30); QCOMPARE(s.occurrenceMultiplier(), 30); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThirtyDays); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily); s.setOccurrencePeriod(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceMultiplier(), 3); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeWeeks); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceMultiplier(), 4); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourWeeks); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceMultiplier(), 5); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); s.setOccurrenceMultiplier(8); QCOMPARE(s.occurrenceMultiplier(), 8); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryEightWeeks); s.setOccurrencePeriod(Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth); s.setOccurrencePeriod(Schedule::Occurrence::Monthly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherMonth); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceMultiplier(), 3); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths); s.setOccurrenceMultiplier(4); QCOMPARE(s.occurrenceMultiplier(), 4); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourMonths); s.setOccurrenceMultiplier(5); QCOMPARE(s.occurrenceMultiplier(), 5); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly); s.setOccurrenceMultiplier(6); QCOMPARE(s.occurrenceMultiplier(), 6); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::TwiceYearly); s.setOccurrencePeriod(Schedule::Occurrence::Yearly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); s.setOccurrenceMultiplier(1); QCOMPARE(s.occurrenceMultiplier(), 1); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly); s.setOccurrenceMultiplier(2); QCOMPARE(s.occurrenceMultiplier(), 2); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherYear); s.setOccurrenceMultiplier(3); QCOMPARE(s.occurrenceMultiplier(), 3); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly); // Set occurrence: check occurrence, Period and Multiplier s.setOccurrence(Schedule::Occurrence::Once); QCOMPARE(s.occurrence(), Schedule::Occurrence::Once); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Once); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::Daily); QCOMPARE(s.occurrence(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryThirtyDays); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThirtyDays); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Daily); QCOMPARE(s.occurrenceMultiplier(), 30); s.setOccurrence(Schedule::Occurrence::Weekly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 2); // Fortnightly no longer used: Every other week used instead s.setOccurrence(Schedule::Occurrence::Fortnightly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherWeek); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 2); s.setOccurrence(Schedule::Occurrence::EveryThreeWeeks); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeWeeks); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 3); s.setOccurrence(Schedule::Occurrence::EveryFourWeeks); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourWeeks); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 4); s.setOccurrence(Schedule::Occurrence::EveryEightWeeks); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryEightWeeks); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Weekly); QCOMPARE(s.occurrenceMultiplier(), 8); s.setOccurrence(Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::EveryHalfMonth); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::Monthly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryOtherMonth); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherMonth); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 2); s.setOccurrence(Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 3); // Quarterly no longer used. Every three months used instead s.setOccurrence(Schedule::Occurrence::Quarterly); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryThreeMonths); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 3); s.setOccurrence(Schedule::Occurrence::EveryFourMonths); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryFourMonths); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 4); s.setOccurrence(Schedule::Occurrence::TwiceYearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::TwiceYearly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(s.occurrenceMultiplier(), 6); s.setOccurrence(Schedule::Occurrence::Yearly); QCOMPARE(s.occurrence(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrenceMultiplier(), 1); s.setOccurrence(Schedule::Occurrence::EveryOtherYear); QCOMPARE(s.occurrence(), Schedule::Occurrence::EveryOtherYear); QCOMPARE(s.occurrencePeriod(), Schedule::Occurrence::Yearly); QCOMPARE(s.occurrenceMultiplier(), 2); } void MyMoneyScheduleTest::testSimpleToFromCompoundOccurrence() { // Conversion between Simple and Compound occurrences // Each simple occurrence to compound occurrence Schedule::Occurrence occ; int mult; occ = Schedule::Occurrence::Once; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Once && mult == 1); occ = Schedule::Occurrence::Daily; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Daily && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 1); occ = Schedule::Occurrence::EveryOtherWeek; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 2); occ = Schedule::Occurrence::Fortnightly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 2); occ = Schedule::Occurrence::EveryHalfMonth; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryHalfMonth && mult == 1); occ = Schedule::Occurrence::EveryThreeWeeks; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 3); occ = Schedule::Occurrence::EveryFourWeeks; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 4); occ = Schedule::Occurrence::EveryThirtyDays; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Daily && mult == 30); occ = Schedule::Occurrence::Monthly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 1); occ = Schedule::Occurrence::EveryEightWeeks; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 8); occ = Schedule::Occurrence::EveryOtherMonth; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 2); occ = Schedule::Occurrence::EveryThreeMonths; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 3); occ = Schedule::Occurrence::Quarterly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 3); occ = Schedule::Occurrence::EveryFourMonths; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 4); occ = Schedule::Occurrence::TwiceYearly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 6); occ = Schedule::Occurrence::Yearly; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 1); occ = Schedule::Occurrence::EveryOtherYear; mult = 1; MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 2); // Compound to Simple Occurrences occ = Schedule::Occurrence::Once; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Once && mult == 1); occ = Schedule::Occurrence::Daily; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Daily && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Weekly && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 2; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryOtherWeek && mult == 1); // Schedule::Occurrence::Fortnightly not converted back occ = Schedule::Occurrence::EveryHalfMonth; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryHalfMonth && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 3; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryThreeWeeks && mult == 1); occ = Schedule::Occurrence::Weekly ; mult = 4; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryFourWeeks && mult == 1); occ = Schedule::Occurrence::Daily; mult = 30; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryThirtyDays && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Monthly && mult == 1); occ = Schedule::Occurrence::Weekly; mult = 8; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryEightWeeks && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 2; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryOtherMonth && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 3; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryThreeMonths && mult == 1); // Schedule::Occurrence::Quarterly not converted back occ = Schedule::Occurrence::Monthly; mult = 4; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryFourMonths && mult == 1); occ = Schedule::Occurrence::Monthly; mult = 6; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::TwiceYearly && mult == 1); occ = Schedule::Occurrence::Yearly; mult = 1; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::Yearly && mult == 1); occ = Schedule::Occurrence::Yearly; mult = 2; MyMoneySchedule::compoundToSimpleOccurrence(mult, occ); QVERIFY(occ == Schedule::Occurrence::EveryOtherYear && mult == 1); } void MyMoneyScheduleTest::testProcessingDates() { // There should be no processing calendar defined so // make sure fall back works MyMoneySchedule s; // Check there is no processing caledar defined. QVERIFY(s.processingCalendar() == nullptr); // This should be a processing day. QCOMPARE(s.isProcessingDate(QDate(2009, 12, 31)), true); // This should be a processing day when there is no calendar. QCOMPARE(s.isProcessingDate(QDate(2010, 1, 1)), true); // This should be a non-processing day as it is on a weekend. QCOMPARE(s.isProcessingDate(QDate(2010, 1, 2)), false); } void MyMoneyScheduleTest::testPaidEarlyOneTime() { // this tries to figure out what's wrong with // https://bugs.kde.org/show_bug.cgi?id=231029 MyMoneySchedule sch; QDate paymentInFuture = QDate::currentDate().addDays(7); QString ref_ok = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.isFinished(), true); QCOMPARE(sch.occurrencePeriod(), Schedule::Occurrence::Monthly); QCOMPARE(sch.paymentDates(QDate::currentDate(), QDate::currentDate().addDays(21)).count(), 0); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testAdjustedNextPayment() { MyMoneySchedule s; QDate dueDate(2010, 5, 23); QDate adjustedDueDate(2010, 5, 21); s.setNextDueDate(dueDate); s.setOccurrence(Schedule::Occurrence::Monthly); s.setWeekendOption(Schedule::WeekendOption::MoveBefore); //if adjustedNextPayment works ok with adjusted date prior to the current date, it should return 2010-06-23 QDate nextDueDate(2010, 6, 23); //this is the current behaviour, and it is wrong //QCOMPARE(s.adjustedNextPayment(adjustedDueDate), adjustedDueDate); //this is the expected behaviour QCOMPARE(s.adjustedNextPayment(s.adjustedNextDueDate()), s.adjustedDate(nextDueDate, s.weekendOption())); } void MyMoneyScheduleTest::testReplaceId() { MyMoneySchedule sch; QDate paymentInFuture = QDate::currentDate().addDays(7); QString ref_ok = QString( "\n" "\n" " \n" // krazy:exclude=spelling " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" "\n" ).arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)) .arg(paymentInFuture.toString(Qt::ISODate)); QDomDocument doc; QDomElement node; doc.setContent(ref_ok); node = doc.documentElement().firstChild().toElement(); try { sch = MyMoneySchedule(node); QCOMPARE(sch.transaction().postDate().isValid(), false); QCOMPARE(sch.transaction().splits()[0].accountId(), QLatin1String("A000076")); QCOMPARE(sch.transaction().splits()[1].accountId(), QLatin1String("A000276")); QCOMPARE(sch.replaceId(QLatin1String("A000079"), QLatin1String("A000076")), true); QCOMPARE(sch.transaction().splits()[0].accountId(), QLatin1String("A000079")); QCOMPARE(sch.transaction().splits()[1].accountId(), QLatin1String("A000276")); } catch (const MyMoneyException &) { QFAIL("Unexpected exception"); } } void MyMoneyScheduleTest::testAdjustedWhenItWillEnd() { MyMoneySchedule s; QDate endDate(2011, 8, 13); // this is a nonprocessing day because // it's a Saturday QDate refDate(2011, 8, 10); // just some ref date before the last payment s.setStartDate(endDate.addMonths(-1)); s.setOccurrence(Schedule::Occurrence::Monthly); s.setEndDate(endDate); // the next due date is on this day but the policy is to move the // schedule to the next processing day (Monday) s.setWeekendOption(Schedule::WeekendOption::MoveAfter); s.setNextDueDate(endDate); // the payment should be found between the respective date and one month after QCOMPARE(s.paymentDates(endDate, endDate.addMonths(1)).count(), 1); // the next payment must be the final one QCOMPARE(s.nextPayment(refDate), endDate); // and since it is on a Saturday, the adjusted one must be on the // following Monday QCOMPARE(s.adjustedNextPayment(refDate), endDate.addDays(2)); // reference for Sunday is still OK QCOMPARE(s.adjustedNextPayment(QDate(2011, 8, 14)), endDate.addDays(2)); // but it is finished on Monday (as reference date) QVERIFY(!s.adjustedNextPayment(QDate(2011, 8, 15)).isValid()); // check the # of remaining transactions s.setNextDueDate(endDate.addMonths(-1)); QCOMPARE(s.transactionsRemaining(), 2); } void MyMoneyScheduleTest::testElementNames() { for (auto i = (int)Schedule::Element::Payment; i <= (int)Schedule::Element::Payments; ++i) { auto isEmpty = MyMoneySchedulePrivate::getElName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty element's name " << i; QVERIFY(!isEmpty); } } void MyMoneyScheduleTest::testAttributeNames() { for (auto i = (int)Schedule::Attribute::Name; i < (int)Schedule::Attribute::LastAttribute; ++i) { auto isEmpty = MyMoneySchedulePrivate::getAttrName(static_cast(i)).isEmpty(); if (isEmpty) qWarning() << "Empty attribute's name " << i; QVERIFY(!isEmpty); } } void MyMoneyScheduleTest::testProcessLastDayInMonth() { MyMoneySchedule s; // occurrence is unrelated s.setOccurrence(Schedule::Occurrence::Any); s.setLastDayInMonth(true); s.setNextDueDate(QDate(2010, 1, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2010,1,31)); s.setNextDueDate(QDate(2010, 2, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2010,2,28)); s.setNextDueDate(QDate(2016, 2, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2016,2,29)); s.setNextDueDate(QDate(2016, 4, 1)); QCOMPARE(s.adjustedNextDueDate(), QDate(2016,4,30)); s.setLastDayInMonth(false); QCOMPARE(s.adjustedNextDueDate(), QDate(2016,4,1)); } diff --git a/kmymoney/mymoney/tests/onlinejobadministration-test.cpp b/kmymoney/mymoney/tests/onlinejobadministration-test.cpp index 67cae873f..35458f628 100644 --- a/kmymoney/mymoney/tests/onlinejobadministration-test.cpp +++ b/kmymoney/mymoney/tests/onlinejobadministration-test.cpp @@ -1,74 +1,74 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "onlinejobadministration-test.h" #include #include "onlinejobadministration.h" #include "mymoney/mymoneyfile.h" #include "mymoneyaccount.h" -#include "mymoney/storage/mymoneyseqaccessmgr.h" +#include "mymoney/storage/mymoneystoragemgr.h" #include "onlinetasks/dummy/tasks/dummytask.h" #include "mymoneyenums.h" QTEST_GUILESS_MAIN(onlineJobAdministrationTest) void onlineJobAdministrationTest::initTestCase() { file = MyMoneyFile::instance(); - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; file->attachStorage(storage); try { MyMoneyAccount account = MyMoneyAccount(); account.setName("Test Account"); account.setAccountType(eMyMoney::Account::Type::Savings); MyMoneyAccount asset = file->asset(); MyMoneyFileTransaction transaction; file->addAccount(account , asset); accountId = account.id(); transaction.commit(); } catch (const MyMoneyException& ex) { QFAIL(qPrintable("Unexpected exception " + ex.what())); } } void onlineJobAdministrationTest::cleanupTestCase() { file->detachStorage(storage); delete storage; } void onlineJobAdministrationTest::init() { qDeleteAll(onlineJobAdministration::instance()->m_onlineTasks); onlineJobAdministration::instance()->m_onlineTasks.clear(); } void onlineJobAdministrationTest::getSettings() { } void onlineJobAdministrationTest::registerOnlineTask() { dummyTask *task = new dummyTask; onlineJobAdministration::instance()->registerOnlineTask(task); QCOMPARE(onlineJobAdministration::instance()->m_onlineTasks.count(), 1); QVERIFY(onlineJobAdministration::instance()->m_onlineTasks.value(task->taskName())); } diff --git a/kmymoney/mymoney/tests/onlinejobadministration-test.h b/kmymoney/mymoney/tests/onlinejobadministration-test.h index ceae0eec3..3e3bf4ebf 100644 --- a/kmymoney/mymoney/tests/onlinejobadministration-test.h +++ b/kmymoney/mymoney/tests/onlinejobadministration-test.h @@ -1,45 +1,45 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef ONLINEJOBADMINISTRATIONTEST_H #define ONLINEJOBADMINISTRATIONTEST_H #include #include class MyMoneyFile; -class IMyMoneyStorage; +class MyMoneyStorageMgr; #define KMM_MYMONEY_UNIT_TESTABLE friend class onlineJobAdministrationTest; class onlineJobAdministrationTest : public QObject { Q_OBJECT - IMyMoneyStorage* storage; + MyMoneyStorageMgr* storage; MyMoneyFile* file; QString accountId; private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void init(); void getSettings(); void registerOnlineTask(); }; #endif // ONLINEJOBADMINISTRATIONTEST_H diff --git a/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.cpp b/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.cpp index b8f651211..07222e3ca 100644 --- a/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.cpp +++ b/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.cpp @@ -1,330 +1,330 @@ /*************************************************************************** csvimportercore.cpp ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "csvimportercore-test.h" #include #include "mymoneyfile.h" #include "mymoneyaccount.h" -#include +#include #include "csvimportercore.h" #include "csvimporttestcommon.h" QTEST_GUILESS_MAIN(CSVImporterCoreTest) void CSVImporterCoreTest::initTestCase() { // setup the MyMoneyMoney locale settings according to the KDE settings MyMoneyMoney::setThousandSeparator(QLocale().groupSeparator()); MyMoneyMoney::setDecimalSeparator(QLocale().decimalPoint()); } void CSVImporterCoreTest::init() { - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); csvImporter = new CSVImporterCore; csvImporter->m_mapSymbolName.insert("STK1", "Stock 1"); csvImporter->m_mapSymbolName.insert("STK2", "Stock 2"); csvImporter->m_mapSymbolName.insert("STK3", "Stock 3"); investmentProfile = new InvestmentProfile ("investment", 106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Semicolon, TextDelimiter::DoubleQuote, DecimalSymbol::Dot, QMap {{Column::Date, 0}, {Column::Name, 1}, {Column::Type, 2}, {Column::Quantity, 3}, {Column::Price, 4}, {Column::Amount, 5}}, 2, QMap { {eMyMoney::Transaction::Action::Buy, QStringList {"buy"}}, {eMyMoney::Transaction::Action::Sell, QStringList {"sell"}}} ); pricesProfile = new PricesProfile ("price source", 106, 1, 0, DateFormat::YearMonthDay, FieldDelimiter::Comma, TextDelimiter::DoubleQuote, DecimalSymbol::Dot, QMap{{Column::Date, 0}, {Column::Price, 4}}, 2, Profile::StockPrices); amountProfile = new BankingProfile ("amount", 106, 1, 0, DateFormat::MonthDayYear, FieldDelimiter::Comma, TextDelimiter::DoubleQuote, DecimalSymbol::Dot, QMap{{Column::Date, 1}, {Column::Memo, 2}, {Column::Amount, 3}, {Column::Category, 4}}, false); debitCreditProfile = new BankingProfile ("debit credit", 106, 1, 0, DateFormat::MonthDayYear, FieldDelimiter::Comma, TextDelimiter::DoubleQuote, DecimalSymbol::Dot, QMap{{Column::Date, 1}, {Column::Memo, 2}, {Column::Debit, 3}, {Column::Credit, 4}, {Column::Category, 5}}, false); } void CSVImporterCoreTest::cleanup() { delete investmentProfile; delete pricesProfile; delete amountProfile; delete csvImporter; file->detachStorage(storage); delete storage; } void CSVImporterCoreTest::testBasicPriceTable() { QString csvContent; csvContent += QLatin1String("Date,Open,High,Low,Close,Volume\n"); csvContent += QLatin1String("2017-08-01,1.23,2.34,3.45,4.56,2\n"); csvContent += QLatin1String("2017-08-02,5.67,6.78,7.89,9.10,2\n"); csvContent += QLatin1String("2017-08-03,2.23,3.34,4.45,5.56,2\n"); QString filename("basic-price-table.csv"); writeStatementToCSV(csvContent, filename); pricesProfile->m_securityName = QLatin1String("APPLE"); auto st = csvImporter->unattendedImport(filename, pricesProfile); QVERIFY(st.m_eType == eMyMoney::Statement::Type::None); QVERIFY(st.m_listPrices.count() == 3); QVERIFY(st.m_listPrices[2].m_date == QDate(2017, 8, 3)); QCOMPARE(st.m_listPrices[2].m_amount.toString(), MyMoneyMoney(5.56).toString()); QVERIFY(st.m_listPrices[2].m_sourceName == "price source"); QVERIFY(st.m_listPrices[2].m_strSecurity == pricesProfile->m_securityName); } void CSVImporterCoreTest::testPriceFractionSetting() { QString csvContent; csvContent += QLatin1String("Date;Open;High;Low;Close;Volume\n"); csvContent += QLatin1String(";;;;;\n"); csvContent += QLatin1String("8/1/2017;1,234;2,345;3,456;4,567;2\n"); csvContent += QLatin1String("8/2/2017;5,679;6,789;7,891;9,101;2\n"); csvContent += QLatin1String("8/3/2017;2,234;3,345;4,456;5,567;2\n"); csvContent += QLatin1String("8/4/2017;3,456;4,567;5,678;6,789;2\n"); QString filename("price-fraction-setting.csv"); writeStatementToCSV(csvContent, filename); pricesProfile->m_securityName = QLatin1String("APPLE"); pricesProfile->m_dateFormat = DateFormat::MonthDayYear; pricesProfile->m_fieldDelimiter = FieldDelimiter::Semicolon; pricesProfile->m_decimalSymbol = DecimalSymbol::Comma; pricesProfile->m_priceFraction = 1; // price *= 0.1 pricesProfile->m_startLine = 2; pricesProfile->m_trailerLines = 1; auto st = csvImporter->unattendedImport(filename, pricesProfile); QVERIFY(st.m_listPrices.count() == 3); QVERIFY(st.m_listPrices[2].m_date == QDate(2017, 8, 3)); QVERIFY(st.m_listPrices[2].m_amount == MyMoneyMoney(0.5567, 10000)); // user reported that visible price (5.567) should be treated as a fraction of real price (0.5567) } void CSVImporterCoreTest::testImportByDebitCredit() { QString csvContent; csvContent += QLatin1String("\"Trans Date\",\"Post Date\",\"Description\",\"Debit\",\"Credit\",\"Category\"\n"); csvContent += QLatin1String("05/16/2016,05/17/2016,FOO1,1.234,0.00,BAR\n"); // debit is intentionally positive here csvContent += QLatin1String("06/17/2016,06/18/2016,FOO2,0,90.12,BAR\n"); csvContent += QLatin1String("07/18/2016,07/19/2016,FOO3,-910.12,,BAR\n"); csvContent += QLatin1String("08/19/2016,08/20/2016,FOO4,,,BAR\n"); csvContent += QLatin1String("09/20/2016,09/21/2016,FOO5,0,0,BAR\n"); QString filename("import-by-debit-credit.csv"); writeStatementToCSV(csvContent, filename); auto st = csvImporter->unattendedImport(filename, debitCreditProfile); QVERIFY(st.m_listTransactions.count() == 5); QVERIFY(st.m_listTransactions[0].m_amount == MyMoneyMoney(-1.234, 1000)); QVERIFY(st.m_listTransactions[0].m_listSplits.count() == 1); QVERIFY(st.m_listTransactions[0].m_listSplits.first().m_amount == MyMoneyMoney(1.234, 1000)); QVERIFY(st.m_listTransactions[1].m_amount == MyMoneyMoney(90.12)); QVERIFY(st.m_listTransactions[2].m_amount == MyMoneyMoney(-910.12)); QVERIFY(st.m_listTransactions[3].m_amount == MyMoneyMoney()); QVERIFY(st.m_listTransactions[4].m_amount == MyMoneyMoney()); } void CSVImporterCoreTest::testImportByAmount() { QString csvContent; csvContent += QLatin1String("\"Trans Date\",\"Post Date\",\"Description\",\"Amount\",\"Category\"\n"); csvContent += QLatin1String("05/16/2016,05/17/2016,FOO1,1.234,BAR\n"); csvContent += QLatin1String("06/17/2016,06/18/2016,FOO2,56.78,BAR\n"); csvContent += QLatin1String("07/18/2016,07/19/2016,FOO3,910.12,BAR\n"); QString filename("import-by-amount.csv"); writeStatementToCSV(csvContent, filename); auto st = csvImporter->unattendedImport(filename, amountProfile); QVERIFY(st.m_listTransactions.count() == 3); QVERIFY(st.m_listTransactions[1].m_datePosted == QDate(2016, 6, 18)); QVERIFY(st.m_listTransactions[1].m_strMemo == "FOO2"); QVERIFY(st.m_listTransactions[1].m_listSplits.count() == 1); QVERIFY(st.m_listTransactions[1].m_listSplits.first().m_strCategoryName == "BAR"); QVERIFY(st.m_listTransactions[1].m_listSplits.first().m_amount == MyMoneyMoney(-56.78)); QVERIFY(st.m_listTransactions[1].m_amount == MyMoneyMoney(56.78)); } void CSVImporterCoreTest::testImportByName() { auto stockNames = csvImporter->m_mapSymbolName.values(); auto stockSymbols = csvImporter->m_mapSymbolName.keys(); auto csvContent = csvDataset(0); QString filename("import-by-name.csv"); writeStatementToCSV(csvContent, filename); auto st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_eType == eMyMoney::Statement::Type::Investment); QVERIFY(st.m_listSecurities.count() == 3); QVERIFY(st.m_listSecurities[0].m_strName == stockNames.at(0)); QVERIFY(st.m_listSecurities[0].m_strSymbol == stockSymbols.at(0)); QVERIFY(st.m_listTransactions.count() == 3); QVERIFY(st.m_listTransactions[0].m_datePosted == QDate(2017, 8, 1)); QVERIFY(st.m_listTransactions[0].m_amount == MyMoneyMoney(-125)); // KMM handled correctly positive amount in buy transaction QVERIFY(st.m_listTransactions[0].m_shares == MyMoneyMoney(100)); QVERIFY(st.m_listTransactions[0].m_strSecurity == stockNames.at(0)); QVERIFY(st.m_listTransactions[0].m_strSymbol == stockSymbols.at(0)); } void CSVImporterCoreTest::testImportBySymbol() { auto stockNames = csvImporter->m_mapSymbolName.values(); auto stockSymbols = csvImporter->m_mapSymbolName.keys(); auto csvContent = csvDataset(0); for (auto i = 0; i < stockNames.count(); ++i) csvContent.replace(stockNames.at(i), stockSymbols.at(i)); csvContent.replace("Name", "Symbol"); QString filename("import-by-symbol.csv"); writeStatementToCSV(csvContent, filename); investmentProfile->m_colTypeNum.remove(Column::Name); investmentProfile->m_colNumType.remove(1); investmentProfile->m_colTypeNum.insert(Column::Symbol, 1); investmentProfile->m_colNumType.insert(1, Column::Symbol); auto st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_listSecurities.count() == 3); QVERIFY(st.m_listTransactions.count() == 3); QVERIFY(st.m_listTransactions[0].m_strSecurity == stockNames.at(0)); QVERIFY(st.m_listTransactions[0].m_strSymbol == stockSymbols.at(0)); } void CSVImporterCoreTest::testFeeColumn() { auto csvContent = csvDataset(0); QString filename("fee-column.csv"); writeStatementToCSV(csvContent, filename); investmentProfile->m_colTypeNum.insert(Column::Fee, 6); investmentProfile->m_colNumType.insert(6, Column::Fee); auto st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_listTransactions[0].m_amount == MyMoneyMoney(-129)); // new_amount = original_amount + fee QVERIFY(st.m_listTransactions[0].m_fees == MyMoneyMoney(4)); // fee taken literally QVERIFY(st.m_listTransactions[1].m_amount == MyMoneyMoney(450)); QVERIFY(st.m_listTransactions[1].m_fees == MyMoneyMoney(6)); investmentProfile->m_feeIsPercentage = true; st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_listTransactions[0].m_amount == MyMoneyMoney(-130)); // new_amount = original_amount (1 + fee/100) QVERIFY(st.m_listTransactions[0].m_fees == MyMoneyMoney(5)); // new_fee = original_amount * fee/100 } void CSVImporterCoreTest::testAutoDecimalSymbol() { auto csvContent = csvDataset(0); csvContent += QLatin1String("2017-08-04-12.02.10;Stock 1;sell;101;1.25;126,25;4\n"); // mixed decimal symbols are on purpose here QString filename("auto-decimal-symbol.csv"); writeStatementToCSV(csvContent, filename); investmentProfile->m_decimalSymbol = DecimalSymbol::Auto; auto st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_listTransactions.count() == 4); QVERIFY(st.m_listTransactions[3].m_datePosted == QDate(2017, 8, 4)); QVERIFY(st.m_listTransactions[3].m_amount == MyMoneyMoney(126.25)); QVERIFY(st.m_listTransactions[3].m_shares == MyMoneyMoney(101)); } void CSVImporterCoreTest::testInvAccountAutodetection() { MyMoneyFileTransaction ft; makeAccount("Eas", "123", eMyMoney::Account::Type::Investment, QDate(2017, 8, 1), file->asset().id()); makeAccount("BigInvestments", "", eMyMoney::Account::Type::Investment, QDate(2017, 8, 1), file->asset().id()); makeAccount("BigInvestments", "1234567890", eMyMoney::Account::Type::Investment, QDate(2017, 8, 1), file->asset().id()); makeAccount("EasyAccount", "", eMyMoney::Account::Type::Investment, QDate(2017, 8, 1), file->asset().id()); auto toBeClosedAccID = makeAccount("EasyAccount", "123456789", eMyMoney::Account::Type::Investment, QDate(2017, 8, 1), file->asset().id()); // this account has the most characters matching the statement auto accID = makeAccount("Easy", "123456789", eMyMoney::Account::Type::Investment, QDate(2017, 8, 1), file->asset().id()); makeAccount("EasyAccount", "123456789", eMyMoney::Account::Type::Checkings, QDate(2017, 8, 1), file->asset().id()); ft.commit(); auto csvContent = csvDataset(0); csvContent.prepend("Bank name:;\n"); csvContent.prepend("BigInvestments;\n"); csvContent.prepend("Account number:;\n"); csvContent.prepend("123456789;\n"); csvContent.prepend("Account name:;\n"); csvContent.prepend("EasyAccount;\n"); QString filename("account-autodetection.csv"); writeStatementToCSV(csvContent, filename); investmentProfile->m_startLine = 7; csvImporter->m_autodetect[AutoAccountInvest] = true; auto st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_listTransactions.count() == 3); QVERIFY(st.m_accountId == toBeClosedAccID); // closed account shouldn't be autodetected auto closedAcc = file->account(toBeClosedAccID); ft.restart(); closedAcc.setClosed(true); file->modifyAccount(closedAcc); ft.commit(); st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_accountId == accID); } void CSVImporterCoreTest::testCalculatedFeeColumn() { auto csvContent = csvDataset(0); QString filename("calculated-fee-column.csv"); writeStatementToCSV(csvContent, filename); investmentProfile->m_feeRate = QLatin1String("4"); investmentProfile->m_feeIsPercentage = true; // fee is calculated always as percentage auto st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_listTransactions[0].m_amount == MyMoneyMoney(-130)); QVERIFY(st.m_listTransactions[0].m_fees == MyMoneyMoney(5)); investmentProfile->m_minFee = QLatin1String("6"); investmentProfile->m_colTypeNum.remove(Column::Fee); // hack st = csvImporter->unattendedImport(filename, investmentProfile); QVERIFY(st.m_listTransactions[0].m_amount == MyMoneyMoney(-131)); QVERIFY(st.m_listTransactions[0].m_fees == MyMoneyMoney(6)); // minimal fee is 6 now, so fee of 5 from above test must be increased to 6 } diff --git a/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.h b/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.h index e932d381e..0d1fb519b 100644 --- a/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.h +++ b/kmymoney/plugins/csv/import/core/tests/csvimportercore-test.h @@ -1,55 +1,55 @@ /*************************************************************************** csvimportercore.h ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef CSVIMPORTERCORETEST_H #define CSVIMPORTERCORETEST_H #include class CSVImporterCore; class BankingProfile; class PricesProfile; class InvestmentProfile; class MyMoneyFile; -class MyMoneySeqAccessMgr; +class MyMoneyStorageMgr; class CSVImporterCoreTest : public QObject { Q_OBJECT CSVImporterCore *csvImporter; BankingProfile *debitCreditProfile; BankingProfile *amountProfile; PricesProfile *pricesProfile; InvestmentProfile *investmentProfile; MyMoneyFile *file; - MyMoneySeqAccessMgr *storage; + MyMoneyStorageMgr *storage; private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testBasicPriceTable(); void testPriceFractionSetting(); void testImportByDebitCredit(); void testImportByAmount(); void testImportByName(); void testImportBySymbol(); void testFeeColumn(); void testAutoDecimalSymbol(); void testInvAccountAutodetection(); void testCalculatedFeeColumn(); }; #endif diff --git a/kmymoney/plugins/gnc/import/gncimporter.cpp b/kmymoney/plugins/gnc/import/gncimporter.cpp index b76998c1b..35d328371 100644 --- a/kmymoney/plugins/gnc/import/gncimporter.cpp +++ b/kmymoney/plugins/gnc/import/gncimporter.cpp @@ -1,95 +1,95 @@ /*************************************************************************** gncimporter.cpp ------------------- copyright : (C) 2017 by Łukasz Wojniłowicz email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "gncimporter.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneygncreader.h" #include "viewinterface.h" #include "mymoneyfile.h" #include "mymoneyexception.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" #include "kmymoneyglobalsettings.h" class MyMoneyStatement; GNCImporter::GNCImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "gncimporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args) setComponentName("gncimporter", i18n("GnuCash importer")); setXMLFile("gncimporter.rc"); createActions(); // For information, announce that we have been loaded. qDebug("Plugins: gncimporter loaded"); } GNCImporter::~GNCImporter() { qDebug("Plugins: gncimporter unloaded"); } void GNCImporter::injectExternalSettings(KMyMoneySettings* p) { KMyMoneyGlobalSettings::injectExternalSettings(p); } void GNCImporter::createActions() { m_action = actionCollection()->addAction("file_import_gnc"); m_action->setText(i18n("GnuCash...")); connect(m_action, &QAction::triggered, this, &GNCImporter::slotGNCImport); } void GNCImporter::slotGNCImport() { m_action->setEnabled(false); if (viewInterface()->fileOpen()) { KMessageBox::information(nullptr, i18n("You cannot import GnuCash data into an existing file. Please close it.")); m_action->setEnabled(true); return; } auto url = QFileDialog::getOpenFileUrl(nullptr, QString(), QUrl(), i18n("GnuCash files (*.gnucash *.xac *.gnc);;All files (*)")); if (url.isLocalFile()) { auto pReader = new MyMoneyGncReader; if (viewInterface()->readFile(url, pReader)) viewInterface()->slotRefreshViews(); } m_action->setEnabled(true); } K_PLUGIN_FACTORY_WITH_JSON(GNCImporterFactory, "gncimporter.json", registerPlugin();) #include "gncimporter.moc" diff --git a/kmymoney/plugins/gnc/import/mymoneygncreader.cpp b/kmymoney/plugins/gnc/import/mymoneygncreader.cpp index d45ea904a..7e527001d 100644 --- a/kmymoney/plugins/gnc/import/mymoneygncreader.cpp +++ b/kmymoney/plugins/gnc/import/mymoneygncreader.cpp @@ -1,2656 +1,2655 @@ /*************************************************************************** mymoneygncreader - description ------------------- begin : Wed Mar 3 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneygncreader.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #ifndef _GNCFILEANON #include #include #endif #include // ---------------------------------------------------------------------------- // Third party Includes // ------------------------------------------------------------Box21---------------- // Project Includes #include -#include "imymoneyserialize.h" +#include "mymoneystoragemgr.h" #ifndef _GNCFILEANON -#include "storage/imymoneystorage.h" #include "kmymoneyutils.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyschedule.h" #include "mymoneyprice.h" #include "mymoneypayee.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyexception.h" #include "kgncimportoptionsdlg.h" #include "kgncpricesourcedlg.h" #include "keditscheduledlg.h" #include "kmymoneyedit.h" #include "kmymoneymoneyvalidator.h" #define TRY try #define CATCH catch (const MyMoneyException &) #define PASS catch (const MyMoneyException &) { throw; } #else #include "mymoneymoney.h" #include // #define i18n QObject::tr #define TRY #define CATCH #define PASS #define MYMONEYEXCEPTION QString #define MyMoneyException QString #define PACKAGE "KMyMoney" #endif // _GNCFILEANON #include "mymoneyenums.h" using namespace eMyMoney; // init static variables double MyMoneyGncReader::m_fileHideFactor = 0.0; double GncObject::m_moneyHideFactor; // user options void MyMoneyGncReader::setOptions() { #ifndef _GNCFILEANON KGncImportOptionsDlg dlg; // display the dialog to allow the user to set own options if (dlg.exec()) { // set users input options m_dropSuspectSchedules = dlg.scheduleOption(); m_investmentOption = dlg.investmentOption(); m_useFinanceQuote = dlg.quoteOption(); m_useTxNotes = dlg.txNotesOption(); m_decoder = dlg.decodeOption(); gncdebug = dlg.generalDebugOption(); xmldebug = dlg.xmlDebugOption(); bAnonymize = dlg.anonymizeOption(); } else { // user declined, so set some sensible defaults m_dropSuspectSchedules = false; // investment option - 0, create investment a/c per stock a/c, 1 = single new investment account, 2 = prompt for each stock // option 2 doesn't really work too well at present m_investmentOption = 0; m_useFinanceQuote = false; m_useTxNotes = false; m_decoder = 0; gncdebug = false; // general debug messages xmldebug = false; // xml trace bAnonymize = false; // anonymize input } // no dialog option for the following; it will set base currency, and print actual XML data developerDebug = false; // set your fave currency here to save getting that enormous dialog each time you run a test // especially if you have to scroll down to USD... if (developerDebug) m_storage->setValue("kmm-baseCurrency", "GBP"); #endif // _GNCFILEANON } GncObject::GncObject() : pMain(0), m_subElementList(0), m_subElementListCount(0), m_dataElementList(0), m_dataElementListCount(0), m_dataPtr(0), m_state(0), m_anonClassList(0), m_anonClass(0) { } // Check that the current element is of a version we are coded for void GncObject::checkVersion(const QString& elName, const QXmlAttributes& elAttrs, const map_elementVersions& map) { TRY { if (map.contains(elName)) { // if it's not in the map, there's nothing to check if (!map[elName].contains(elAttrs.value("version"))) { QString em = Q_FUNC_INFO + i18n(": Sorry. This importer cannot handle version %1 of element %2" , elAttrs.value("version"), elName); throw MYMONEYEXCEPTION(em); } } return ; } PASS } // Check if this element is in the current object's sub element list GncObject *GncObject::isSubElement(const QString& elName, const QXmlAttributes& elAttrs) { TRY { uint i; GncObject *next = 0; for (i = 0; i < m_subElementListCount; i++) { if (elName == m_subElementList[i]) { m_state = i; next = startSubEl(); // go create the sub object if (next != 0) { next->initiate(elName, elAttrs); // initialize it next->m_elementName = elName; // save it's name so we can identify the end } break; } } return (next); } PASS } // Check if this element is in the current object's data element list bool GncObject::isDataElement(const QString &elName, const QXmlAttributes& elAttrs) { TRY { uint i; for (i = 0; i < m_dataElementListCount; i++) { if (elName == m_dataElementList[i]) { m_state = i; dataEl(elAttrs); // go set the pointer so the data can be stored return (true); } } m_dataPtr = 0; // we don't need this, so make sure we don't store extraneous data return (false); } PASS } // return the variable string, decoded if required QString GncObject::var(int i) const { /* This code was needed because the Qt3 XML reader apparently did not process the encoding parameter in the m_decoder == 0 ? m_v[i] : pMain->m_decoder->toUnicode(m_v[i].toUtf8())); } const QString GncObject::getKvpValue(const QString& key, const QString& type) const { QList::const_iterator it; // first check for exact match for (it = m_kvpList.begin(); it != m_kvpList.end(); ++it) { if (((*it).key() == key) && ((type.isEmpty()) || ((*it).type() == type))) return (*it).value(); } // then for partial match for (it = m_kvpList.begin(); it != m_kvpList.end(); ++it) { if (((*it).key().contains(key)) && ((type.isEmpty()) || ((*it).type() == type))) return (*it).value(); } return (QString()); } void GncObject::adjustHideFactor() { m_moneyHideFactor = pMain->m_fileHideFactor * (1.0 + (int)(200.0 * rand() / (RAND_MAX + 1.0))) / 100.0; } // data anonymizer QString GncObject::hide(QString data, unsigned int anonClass) { TRY { if (!pMain->bAnonymize) return (data); // no anonymizing required // counters used to generate names for anonymizer static int nextAccount; static int nextEquity; static int nextPayee; static int nextSched; static QMap anonPayees; // to check for duplicate payee names static QMap anonStocks; // for reference to equities QString result(data); QMap::const_iterator it; MyMoneyMoney in, mresult; switch (anonClass) { case ASIS: // this is not personal data break; case SUPPRESS: // this is personal and is not essential result = ""; break; case NXTACC: // generate account name result = ki18n("Account%1").subs(++nextAccount, -6).toString(); break; case NXTEQU: // generate/return an equity name it = anonStocks.constFind(data); if (it == anonStocks.constEnd()) { result = ki18n("Stock%1").subs(++nextEquity, -6).toString(); anonStocks.insert(data, result); } else { result = (*it); } break; case NXTPAY: // generate/return a payee name it = anonPayees.constFind(data); if (it == anonPayees.constEnd()) { result = ki18n("Payee%1").subs(++nextPayee, -6).toString(); anonPayees.insert(data, result); } else { result = (*it); } break; case NXTSCHD: // generate a schedule name result = ki18n("Schedule%1").subs(++nextSched, -6).toString(); break; case MONEY1: in = MyMoneyMoney(data); if (data == "-1/0") in = MyMoneyMoney(); // spurious gnucash data - causes a crash sometimes mresult = MyMoneyMoney(m_moneyHideFactor) * in; mresult.convert(10000); result = mresult.toString(); break; case MONEY2: in = MyMoneyMoney(data); if (data == "-1/0") in = MyMoneyMoney(); mresult = MyMoneyMoney(m_moneyHideFactor) * in; mresult.convert(10000); mresult.setThousandSeparator(' '); result = mresult.formatMoney("", 2); break; } return (result); } PASS } // dump current object data values // only called if gncdebug set void GncObject::debugDump() { uint i; qDebug() << "Object" << m_elementName; for (i = 0; i < m_dataElementListCount; i++) { qDebug() << m_dataElementList[i] << "=" << m_v[i]; } } //***************************************************************** GncFile::GncFile() { static const QString subEls[] = {"gnc:book", "gnc:count-data", "gnc:commodity", "price", "gnc:account", "gnc:transaction", "gnc:template-transactions", "gnc:schedxaction" }; m_subElementList = subEls; m_subElementListCount = END_FILE_SELS; m_dataElementListCount = 0; m_processingTemplates = false; m_bookFound = false; } GncFile::~GncFile() {} GncObject *GncFile::startSubEl() { TRY { if (pMain->xmldebug) qDebug("File start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case BOOK: if (m_bookFound) throw MYMONEYEXCEPTION(i18n("This version of the importer cannot handle multi-book files.")); m_bookFound = true; break; case COUNT: next = new GncCountData; break; case CMDTY: next = new GncCommodity; break; case PRICE: next = new GncPrice; break; case ACCT: // accounts within the template section are ignored if (!m_processingTemplates) next = new GncAccount; break; case TX: next = new GncTransaction(m_processingTemplates); break; case TEMPLATES: m_processingTemplates = true; break; case SCHEDULES: m_processingTemplates = false; next = new GncSchedule; break; default: throw MYMONEYEXCEPTION("GncFile rcvd invalid state"); } return (next); } PASS } void GncFile::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("File end subel"); if (!m_processingTemplates) delete subObj; // template txs must be saved awaiting schedules m_dataPtr = 0; return ; } //****************************************** GncDate ********************************************* GncDate::GncDate() { m_subElementListCount = 0; static const QString dEls[] = {"ts:date", "gdate"}; m_dataElementList = dEls; m_dataElementListCount = END_Date_DELS; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncDate::~GncDate() {} //*************************************GncCmdtySpec*************************************** GncCmdtySpec::GncCmdtySpec() { m_subElementListCount = 0; static const QString dEls[] = {"cmdty:space", "cmdty:id"}; m_dataElementList = dEls; m_dataElementListCount = END_CmdtySpec_DELS; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncCmdtySpec::~GncCmdtySpec() {} QString GncCmdtySpec::hide(QString data, unsigned int) { // hide equity names, but not currency names unsigned int newClass = ASIS; switch (m_state) { case CMDTYID: if (!isCurrency()) newClass = NXTEQU; } return (GncObject::hide(data, newClass)); } //************* GncKvp******************************************** GncKvp::GncKvp() { m_subElementListCount = END_Kvp_SELS; static const QString subEls[] = {"slot"}; // kvp's may be nested m_subElementList = subEls; m_dataElementListCount = END_Kvp_DELS; static const QString dataEls[] = {"slot:key", "slot:value"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncKvp::~GncKvp() {} void GncKvp::dataEl(const QXmlAttributes& elAttrs) { switch (m_state) { case VALUE: m_kvpType = elAttrs.value("type"); } m_dataPtr = &(m_v[m_state]); if (key().contains("formula")) { m_anonClass = MONEY2; } else { m_anonClass = ASIS; } return ; } GncObject *GncKvp::startSubEl() { if (pMain->xmldebug) qDebug("Kvp start subel m_state %d", m_state); TRY { GncObject *next = 0; switch (m_state) { case KVP: next = new GncKvp; break; default: throw MYMONEYEXCEPTION("GncKvp rcvd invalid m_state "); } return (next); } PASS } void GncKvp::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Kvp end subel"); m_kvpList.append(*(static_cast (subObj))); m_dataPtr = 0; return ; } //*********************************GncLot********************************************* GncLot::GncLot() { m_subElementListCount = 0; m_dataElementListCount = 0; } GncLot::~GncLot() {} //*********************************GncCountData*************************************** GncCountData::GncCountData() { m_subElementListCount = 0; m_dataElementListCount = 0; m_v.append(QString()); // only 1 data item } GncCountData::~GncCountData() {} void GncCountData::initiate(const QString&, const QXmlAttributes& elAttrs) { m_countType = elAttrs.value("cd:type"); m_dataPtr = &(m_v[0]); return ; } void GncCountData::terminate() { int i = m_v[0].toInt(); if (m_countType == "commodity") { pMain->setGncCommodityCount(i); return ; } if (m_countType == "account") { pMain->setGncAccountCount(i); return ; } if (m_countType == "transaction") { pMain->setGncTransactionCount(i); return ; } if (m_countType == "schedxaction") { pMain->setGncScheduleCount(i); return ; } if (i != 0) { if (m_countType == "budget") pMain->setBudgetsFound(true); else if (m_countType.left(7) == "gnc:Gnc") pMain->setSmallBusinessFound(true); else if (pMain->xmldebug) qDebug() << "Unknown count type" << m_countType; } return ; } //*********************************GncCommodity*************************************** GncCommodity::GncCommodity() { m_subElementListCount = 0; static const QString dEls[] = {"cmdty:space", "cmdty:id", "cmdty:name", "cmdty:fraction"}; m_dataElementList = dEls; m_dataElementListCount = END_Commodity_DELS; static const unsigned int anonClasses[] = {ASIS, NXTEQU, SUPPRESS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncCommodity::~GncCommodity() {} void GncCommodity::terminate() { TRY { pMain->convertCommodity(this); return ; } PASS } //************* GncPrice******************************************** GncPrice::GncPrice() { static const QString subEls[] = {"price:commodity", "price:currency", "price:time"}; m_subElementList = subEls; m_subElementListCount = END_Price_SELS; m_dataElementListCount = END_Price_DELS; static const QString dataEls[] = {"price:value"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpCommodity = 0; m_vpCurrency = 0; m_vpPriceDate = 0; } GncPrice::~GncPrice() { delete m_vpCommodity; delete m_vpCurrency; delete m_vpPriceDate; } GncObject *GncPrice::startSubEl() { TRY { GncObject *next = 0; switch (m_state) { case CMDTY: next = new GncCmdtySpec; break; case CURR: next = new GncCmdtySpec; break; case PRICEDATE: next = new GncDate; break; default: throw MYMONEYEXCEPTION("GncPrice rcvd invalid m_state"); } return (next); } PASS } void GncPrice::endSubEl(GncObject *subObj) { TRY { switch (m_state) { case CMDTY: m_vpCommodity = static_cast(subObj); break; case CURR: m_vpCurrency = static_cast(subObj); break; case PRICEDATE: m_vpPriceDate = static_cast(subObj); break; default: throw MYMONEYEXCEPTION("GncPrice rcvd invalid m_state"); } return; } PASS } void GncPrice::terminate() { TRY { pMain->convertPrice(this); return ; } PASS } //************* GncAccount******************************************** GncAccount::GncAccount() { m_subElementListCount = END_Account_SELS; static const QString subEls[] = {"act:commodity", "slot", "act:lots"}; m_subElementList = subEls; m_dataElementListCount = END_Account_DELS; static const QString dataEls[] = {"act:id", "act:name", "act:description", "act:type", "act:parent" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, NXTACC, SUPPRESS, ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpCommodity = 0; } GncAccount::~GncAccount() { delete m_vpCommodity; } GncObject *GncAccount::startSubEl() { TRY { if (pMain->xmldebug) qDebug("Account start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case CMDTY: next = new GncCmdtySpec; break; case KVP: next = new GncKvp; break; case LOTS: next = new GncLot(); pMain->setLotsFound(true); // we don't handle lots; just set flag to report break; default: throw MYMONEYEXCEPTION("GncAccount rcvd invalid m_state"); } return (next); } PASS } void GncAccount::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Account end subel"); switch (m_state) { case CMDTY: m_vpCommodity = static_cast(subObj); break; case KVP: m_kvpList.append(*(static_cast (subObj))); } return ; } void GncAccount::terminate() { TRY { pMain->convertAccount(this); return ; } PASS } //************* GncTransaction******************************************** GncTransaction::GncTransaction(bool processingTemplates) { m_subElementListCount = END_Transaction_SELS; static const QString subEls[] = {"trn:currency", "trn:date-posted", "trn:date-entered", "trn:split", "slot" }; m_subElementList = subEls; m_dataElementListCount = END_Transaction_DELS; static const QString dataEls[] = {"trn:id", "trn:num", "trn:description"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, SUPPRESS, NXTPAY}; m_anonClassList = anonClasses; adjustHideFactor(); m_template = processingTemplates; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpCurrency = 0; m_vpDateEntered = m_vpDatePosted = 0; } GncTransaction::~GncTransaction() { delete m_vpCurrency; delete m_vpDatePosted; delete m_vpDateEntered; } GncObject *GncTransaction::startSubEl() { TRY { if (pMain->xmldebug) qDebug("Transaction start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case CURRCY: next = new GncCmdtySpec; break; case POSTED: case ENTERED: next = new GncDate; break; case SPLIT: if (isTemplate()) { next = new GncTemplateSplit; } else { next = new GncSplit; } break; case KVP: next = new GncKvp; break; default: throw MYMONEYEXCEPTION("GncTransaction rcvd invalid m_state"); } return (next); } PASS } void GncTransaction::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Transaction end subel"); switch (m_state) { case CURRCY: m_vpCurrency = static_cast(subObj); break; case POSTED: m_vpDatePosted = static_cast(subObj); break; case ENTERED: m_vpDateEntered = static_cast(subObj); break; case SPLIT: m_splitList.append(subObj); break; case KVP: m_kvpList.append(*(static_cast (subObj))); } return ; } void GncTransaction::terminate() { TRY { if (isTemplate()) { pMain->saveTemplateTransaction(this); } else { pMain->convertTransaction(this); } return ; } PASS } //************* GncSplit******************************************** GncSplit::GncSplit() { m_subElementListCount = END_Split_SELS; static const QString subEls[] = {"split:reconcile-date"}; m_subElementList = subEls; m_dataElementListCount = END_Split_DELS; static const QString dataEls[] = {"split:id", "split:memo", "split:reconciled-state", "split:value", "split:quantity", "split:account" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, SUPPRESS, ASIS, MONEY1, MONEY1, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpDateReconciled = 0; } GncSplit::~GncSplit() { delete m_vpDateReconciled; } GncObject *GncSplit::startSubEl() { TRY { GncObject *next = 0; switch (m_state) { case RECDATE: next = new GncDate; break; default: throw MYMONEYEXCEPTION("GncTemplateSplit rcvd invalid m_state "); } return (next); } PASS } void GncSplit::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Split end subel"); switch (m_state) { case RECDATE: m_vpDateReconciled = static_cast(subObj); break; } return ; } //************* GncTemplateSplit******************************************** GncTemplateSplit::GncTemplateSplit() { m_subElementListCount = END_TemplateSplit_SELS; static const QString subEls[] = {"slot"}; m_subElementList = subEls; m_dataElementListCount = END_TemplateSplit_DELS; static const QString dataEls[] = {"split:id", "split:memo", "split:reconciled-state", "split:value", "split:quantity", "split:account" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, SUPPRESS, ASIS, MONEY1, MONEY1, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncTemplateSplit::~GncTemplateSplit() {} GncObject *GncTemplateSplit::startSubEl() { if (pMain->xmldebug) qDebug("TemplateSplit start subel m_state %d", m_state); TRY { GncObject *next = 0; switch (m_state) { case KVP: next = new GncKvp; break; default: throw MYMONEYEXCEPTION("GncTemplateSplit rcvd invalid m_state"); } return (next); } PASS } void GncTemplateSplit::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("TemplateSplit end subel"); m_kvpList.append(*(static_cast (subObj))); m_dataPtr = 0; return ; } //************* GncSchedule******************************************** GncSchedule::GncSchedule() { m_subElementListCount = END_Schedule_SELS; static const QString subEls[] = {"sx:start", "sx:last", "sx:end", "gnc:freqspec", "gnc:recurrence", "sx:deferredInstance"}; m_subElementList = subEls; m_dataElementListCount = END_Schedule_DELS; static const QString dataEls[] = {"sx:name", "sx:enabled", "sx:autoCreate", "sx:autoCreateNotify", "sx:autoCreateDays", "sx:advanceCreateDays", "sx:advanceRemindDays", "sx:instanceCount", "sx:num-occur", "sx:rem-occur", "sx:templ-acct" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {NXTSCHD, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpStartDate = m_vpLastDate = m_vpEndDate = 0; m_vpFreqSpec = 0; m_vpRecurrence.clear(); m_vpSchedDef = 0; } GncSchedule::~GncSchedule() { delete m_vpStartDate; delete m_vpLastDate; delete m_vpEndDate; delete m_vpFreqSpec; delete m_vpSchedDef; } GncObject *GncSchedule::startSubEl() { if (pMain->xmldebug) qDebug("Schedule start subel m_state %d", m_state); TRY { GncObject *next = 0; switch (m_state) { case STARTDATE: case LASTDATE: case ENDDATE: next = new GncDate; break; case FREQ: next = new GncFreqSpec; break; case RECURRENCE: next = new GncRecurrence; break; case DEFINST: next = new GncSchedDef; break; default: throw MYMONEYEXCEPTION("GncSchedule rcvd invalid m_state"); } return (next); } PASS } void GncSchedule::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Schedule end subel"); switch (m_state) { case STARTDATE: m_vpStartDate = static_cast(subObj); break; case LASTDATE: m_vpLastDate = static_cast(subObj); break; case ENDDATE: m_vpEndDate = static_cast(subObj); break; case FREQ: m_vpFreqSpec = static_cast(subObj); break; case RECURRENCE: m_vpRecurrence.append(static_cast(subObj)); break; case DEFINST: m_vpSchedDef = static_cast(subObj); break; } return ; } void GncSchedule::terminate() { TRY { pMain->convertSchedule(this); return ; } PASS } //************* GncFreqSpec******************************************** GncFreqSpec::GncFreqSpec() { m_subElementListCount = END_FreqSpec_SELS; static const QString subEls[] = {"gnc:freqspec"}; m_subElementList = subEls; m_dataElementListCount = END_FreqSpec_DELS; static const QString dataEls[] = {"fs:ui_type", "fs:monthly", "fs:daily", "fs:weekly", "fs:interval", "fs:offset", "fs:day" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS }; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncFreqSpec::~GncFreqSpec() {} GncObject *GncFreqSpec::startSubEl() { TRY { if (pMain->xmldebug) qDebug("FreqSpec start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case COMPO: next = new GncFreqSpec; break; default: throw MYMONEYEXCEPTION("GncFreqSpec rcvd invalid m_state"); } return (next); } PASS } void GncFreqSpec::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("FreqSpec end subel"); switch (m_state) { case COMPO: m_fsList.append(subObj); break; } m_dataPtr = 0; return ; } void GncFreqSpec::terminate() { pMain->convertFreqSpec(this); return ; } //************* GncRecurrence******************************************** GncRecurrence::GncRecurrence() : m_vpStartDate(0) { m_subElementListCount = END_Recurrence_SELS; static const QString subEls[] = {"recurrence:start"}; m_subElementList = subEls; m_dataElementListCount = END_Recurrence_DELS; static const QString dataEls[] = {"recurrence:mult", "recurrence:period_type"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncRecurrence::~GncRecurrence() { delete m_vpStartDate; } GncObject *GncRecurrence::startSubEl() { TRY { if (pMain->xmldebug) qDebug("Recurrence start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case STARTDATE: next = new GncDate; break; default: throw MYMONEYEXCEPTION("GncRecurrence rcvd invalid m_state"); } return (next); } PASS } void GncRecurrence::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Recurrence end subel"); switch (m_state) { case STARTDATE: m_vpStartDate = static_cast(subObj); break; } m_dataPtr = 0; return ; } void GncRecurrence::terminate() { pMain->convertRecurrence(this); return ; } QString GncRecurrence::getFrequency() const { // This function converts a gnucash 2.2 recurrence specification into it's previous equivalent // This will all need re-writing when MTE finishes the schedule re-write if (periodType() == "once") return("once"); if ((periodType() == "day") && (mult() == "1")) return("daily"); if (periodType() == "week") { if (mult() == "1") return ("weekly"); if (mult() == "2") return ("bi_weekly"); if (mult() == "4") return ("four-weekly"); } if (periodType() == "month") { if (mult() == "1") return ("monthly"); if (mult() == "2") return ("two-monthly"); if (mult() == "3") return ("quarterly"); if (mult() == "4") return ("tri_annually"); if (mult() == "6") return ("semi_yearly"); if (mult() == "12") return ("yearly"); if (mult() == "24") return ("two-yearly"); } return ("unknown"); } //************* GncSchedDef******************************************** GncSchedDef::GncSchedDef() { // process ing for this sub-object is undefined at the present time m_subElementListCount = 0; m_dataElementListCount = 0; } GncSchedDef::~GncSchedDef() {} /************************************************************************************************ XML Reader ************************************************************************************************/ XmlReader::XmlReader(MyMoneyGncReader *pM) : m_source(0), m_reader(0), m_co(0), pMain(pM), m_headerFound(false) { } void XmlReader::processFile(QIODevice* pDevice) { m_source = new QXmlInputSource(pDevice); // set up the Qt XML reader m_reader = new QXmlSimpleReader; m_reader->setContentHandler(this); // go read the file if (!m_reader->parse(m_source)) { throw MYMONEYEXCEPTION(i18n("Input file cannot be parsed; may be corrupt\n%1", errorString())); } delete m_reader; delete m_source; return ; } // XML handling routines bool XmlReader::startDocument() { m_co = new GncFile; // create initial object, push to stack , pass it the 'main' pointer m_os.push(m_co); m_co->setPm(pMain); m_headerFound = false; #ifdef _GNCFILEANON pMain->oStream << ""; lastType = -1; indentCount = 0; #endif // _GNCFILEANON return (true); } bool XmlReader::startElement(const QString&, const QString&, const QString& elName , const QXmlAttributes& elAttrs) { try { if (pMain->gncdebug) qDebug() << "XML start -" << elName; #ifdef _GNCFILEANON int i; QString spaces; // anonymizer - write data if (elName == "gnc:book" || elName == "gnc:count-data" || elName == "book:id") lastType = -1; pMain->oStream << endl; switch (lastType) { case 0: indentCount += 2; // tricky fall through here case 2: spaces.fill(' ', indentCount); pMain->oStream << spaces.toLatin1(); break; } pMain->oStream << '<' << elName; for (i = 0; i < elAttrs.count(); ++i) { pMain->oStream << ' ' << elAttrs.qName(i) << '=' << '"' << elAttrs.value(i) << '"'; } pMain->oStream << '>'; lastType = 0; #else if ((!m_headerFound) && (elName != "gnc-v2")) throw MYMONEYEXCEPTION(i18n("Invalid header for file. Should be 'gnc-v2'")); m_headerFound = true; #endif // _GNCFILEANON m_co->checkVersion(elName, elAttrs, pMain->m_versionList); // check if this is a sub object element; if so, push stack and initialize GncObject *temp = m_co->isSubElement(elName, elAttrs); if (temp != 0) { m_os.push(temp); m_co = m_os.top(); m_co->setVersion(elAttrs.value("version")); m_co->setPm(pMain); // pass the 'main' pointer to the sub object // return true; // removed, as we hit a return true anyway } #if 0 // check for a data element if (m_co->isDataElement(elName, elAttrs)) return (true); #endif else { // reduced the above to m_co->isDataElement(elName, elAttrs); } } catch (const MyMoneyException &e) { #ifndef _GNCFILEANON // we can't pass on exceptions here coz the XML reader won't catch them and we just abort KMessageBox::error(0, i18n("Import failed:\n\n%1", e.what()), PACKAGE); qWarning("%s", qPrintable(e.what())); #else qWarning("%s", e->toLatin1()); #endif // _GNCFILEANON } return true; // to keep compiler happy } bool XmlReader::endElement(const QString&, const QString&, const QString&elName) { try { if (pMain->xmldebug) qDebug() << "XML end -" << elName; #ifdef _GNCFILEANON QString spaces; switch (lastType) { case 2: indentCount -= 2; spaces.fill(' ', indentCount); pMain->oStream << endl << spaces.toLatin1(); break; } pMain->oStream << "' ; lastType = 2; #endif // _GNCFILEANON m_co->resetDataPtr(); // so we don't get extraneous data loaded into the variables if (elName == m_co->getElName()) { // check if this is the end of the current object if (pMain->gncdebug) m_co->debugDump(); // dump the object data (temp) // call the terminate routine, pop the stack, and advise the parent that it's done m_co->terminate(); GncObject *temp = m_co; m_os.pop(); m_co = m_os.top(); m_co->endSubEl(temp); } return (true); } catch (const MyMoneyException &e) { #ifndef _GNCFILEANON // we can't pass on exceptions here coz the XML reader won't catch them and we just abort KMessageBox::error(0, i18n("Import failed:\n\n%1", e.what()), PACKAGE); qWarning("%s", qPrintable(e.what())); #else qWarning("%s", e->toLatin1()); #endif // _GNCFILEANON } return (true); // to keep compiler happy } bool XmlReader::characters(const QString &data) { if (pMain->xmldebug) qDebug("XML Data received - %d bytes", data.length()); QString pData = data.trimmed(); // data may contain line feeds and indentation spaces if (!pData.isEmpty()) { if (pMain->developerDebug) qDebug() << "XML Data -" << pData; m_co->storeData(pData); //go store it #ifdef _GNCFILEANON QString anonData = m_co->getData(); if (anonData.isEmpty()) anonData = pData; // there must be a Qt standard way of doing the following but I can't ... find it anonData.replace('<', "<"); anonData.replace('>', ">"); anonData.replace('&', "&"); pMain->oStream << anonData; // write original data lastType = 1; #endif // _GNCFILEANON } return (true); } bool XmlReader::endDocument() { #ifdef _GNCFILEANON pMain->oStream << endl << endl; pMain->oStream << "" << endl; pMain->oStream << "" << endl; pMain->oStream << "" << endl; #endif // _GNCFILEANON return (true); } /******************************************************************************************* Main class for this module Controls overall operation of the importer ********************************************************************************************/ //***************** Constructor *********************** MyMoneyGncReader::MyMoneyGncReader() : m_dropSuspectSchedules(0), m_investmentOption(0), m_useFinanceQuote(0), m_useTxNotes(0), gncdebug(0), xmldebug(0), bAnonymize(0), developerDebug(0), m_xr(0), m_progressCallback(0), m_ccCount(0), m_orCount(0), m_scCount(0), m_potentialTransfer(0), m_suspectSchedule(false) { #ifndef _GNCFILEANON m_storage = 0; #endif // _GNCFILEANON // to hold gnucash count data (only used for progress bar) m_gncCommodityCount = m_gncAccountCount = m_gncTransactionCount = m_gncScheduleCount = 0; m_smallBusinessFound = m_budgetsFound = m_lotsFound = false; m_commodityCount = m_priceCount = m_accountCount = m_transactionCount = m_templateCount = m_scheduleCount = 0; m_decoder = 0; // build a list of valid versions static const QString versionList[] = {"gnc:book 2.0.0", "gnc:commodity 2.0.0", "gnc:pricedb 1", "gnc:account 2.0.0", "gnc:transaction 2.0.0", "gnc:schedxaction 1.0.0", "gnc:schedxaction 2.0.0", // for gnucash 2.2 onward "gnc:freqspec 1.0.0", "zzz" // zzz = stopper }; unsigned int i; for (i = 0; versionList[i] != "zzz"; ++i) m_versionList[versionList[i].section(' ', 0, 0)].append(versionList[i].section(' ', 1, 1)); } //***************** Destructor ************************* MyMoneyGncReader::~MyMoneyGncReader() {} //**************************** Main Entry Point ************************************ #ifndef _GNCFILEANON -void MyMoneyGncReader::readFile(QIODevice* pDevice, IMyMoneySerialize* storage) +void MyMoneyGncReader::readFile(QIODevice* pDevice, MyMoneyStorageMgr* storage) { Q_CHECK_PTR(pDevice); Q_CHECK_PTR(storage); - m_storage = dynamic_cast(storage); + m_storage = storage; qDebug("Entering gnucash importer"); setOptions(); // get a file anonymization factor from the user if (bAnonymize) setFileHideFactor(); //m_defaultPayee = createPayee (i18n("Unknown payee")); MyMoneyFile::instance()->attachStorage(m_storage); MyMoneyFileTransaction ft; m_xr = new XmlReader(this); bool blocked = MyMoneyFile::instance()->signalsBlocked(); MyMoneyFile::instance()->blockSignals(true); try { m_xr->processFile(pDevice); terminate(); // do all the wind-up things ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(0, i18n("Import failed:\n\n%1", e.what()), PACKAGE); qWarning("%s", qPrintable(e.what())); } // end catch MyMoneyFile::instance()->blockSignals(blocked); MyMoneyFile::instance()->detachStorage(m_storage); signalProgress(0, 1, i18n("Import complete")); // switch off progress bar delete m_xr; signalProgress(0, 1, i18nc("Application is ready to use", "Ready.")); // application is ready for input qDebug("Exiting gnucash importer"); } #else // Control code for the file anonymizer void MyMoneyGncReader::readFile(QString in, QString out) { QFile pDevice(in); if (!pDevice.open(QIODevice::ReadOnly)) qWarning("Can't open input file"); QFile outFile(out); if (!outFile.open(QIODevice::WriteOnly)) qWarning("Can't open output file"); oStream.setDevice(&outFile); bAnonymize = true; // get a file anonymization factor from the user setFileHideFactor(); m_xr = new XmlReader(this); try { m_xr->processFile(&pDevice); } catch (const MyMoneyException &e) { qWarning("%s", e->toLatin1()); } // end catch delete m_xr; pDevice.close(); outFile.close(); return ; } #include int main(int argc, char ** argv) { QApplication a(argc, argv); MyMoneyGncReader m; QString inFile, outFile; if (argc > 0) inFile = a.argv()[1]; if (argc > 1) outFile = a.argv()[2]; if (inFile.isEmpty()) { inFile = KFileDialog::getOpenFileName("", "Gnucash files(*.nc *)", 0); } if (inFile.isEmpty()) qWarning("Input file required"); if (outFile.isEmpty()) outFile = inFile + ".anon"; m.readFile(inFile, outFile); exit(0); } #endif // _GNCFILEANON void MyMoneyGncReader::setFileHideFactor() { #define MINFILEHIDEF 0.01 #define MAXFILEHIDEF 99.99 srand(QTime::currentTime().second()); // seed randomizer for anonymize m_fileHideFactor = 0.0; while (m_fileHideFactor == 0.0) { m_fileHideFactor = QInputDialog::getDouble(0, i18n("Disguise your wealth"), i18n("Each monetary value on your file will be multiplied by a random number between 0.01 and 1.99\n" "with a different value used for each transaction. In addition, to further disguise the true\n" "values, you may enter a number between %1 and %2 which will be applied to all values.\n" "These numbers will not be stored in the file.", MINFILEHIDEF, MAXFILEHIDEF), (1.0 + (int)(1000.0 * rand() / (RAND_MAX + 1.0))) / 100.0, MINFILEHIDEF, MAXFILEHIDEF, 2); } } #ifndef _GNCFILEANON //********************************* convertCommodity ******************************************* void MyMoneyGncReader::convertCommodity(const GncCommodity *gcm) { Q_CHECK_PTR(gcm); MyMoneySecurity equ; if (m_commodityCount == 0) signalProgress(0, m_gncCommodityCount, i18n("Loading commodities...")); if (!gcm->isCurrency()) { // currencies should not be present here but... equ.setName(gcm->name()); equ.setTradingSymbol(gcm->id()); equ.setTradingMarket(gcm->space()); // the 'space' may be market or quote source, dep on what the user did // don't set the source here since he may not want quotes //equ.setValue ("kmm-online-source", gcm->space()); // we don't know, so use it as both equ.setTradingCurrency(""); // not available here, will set from pricedb or transaction equ.setSecurityType(Security::Type::Stock); // default to it being a stock //tell the storage objects we have a new equity object. equ.setSmallestAccountFraction(gcm->fraction().toInt()); m_storage->addSecurity(equ); //assign the gnucash id as the key into the map to find our id if (gncdebug) qDebug() << "mapping, key =" << gcm->id() << "id =" << equ.id(); m_mapEquities[gcm->id().toUtf8()] = equ.id(); } signalProgress(++m_commodityCount, 0); return ; } //******************************* convertPrice ************************************************ void MyMoneyGncReader::convertPrice(const GncPrice *gpr) { Q_CHECK_PTR(gpr); // add this to our price history if (m_priceCount == 0) signalProgress(0, 1, i18n("Loading prices...")); MyMoneyMoney rate(convBadValue(gpr->value())); if (gpr->commodity()->isCurrency()) { MyMoneyPrice exchangeRate(gpr->commodity()->id().toUtf8(), gpr->currency()->id().toUtf8(), gpr->priceDate(), rate, i18n("Imported History")); if (!exchangeRate.rate(QString()).isZero()) m_storage->addPrice(exchangeRate); } else { MyMoneySecurity e = m_storage->security(m_mapEquities[gpr->commodity()->id().toUtf8()]); if (gncdebug) qDebug() << "Searching map, key = " << gpr->commodity()->id() << ", found id =" << e.id().data(); e.setTradingCurrency(gpr->currency()->id().toUtf8()); MyMoneyPrice stockPrice(e.id(), gpr->currency()->id().toUtf8(), gpr->priceDate(), rate, i18n("Imported History")); if (!stockPrice.rate(QString()).isZero()) m_storage->addPrice(stockPrice); m_storage->modifySecurity(e); } signalProgress(++m_priceCount, 0); return ; } //*********************************convertAccount **************************************** void MyMoneyGncReader::convertAccount(const GncAccount* gac) { Q_CHECK_PTR(gac); TRY { // we don't care about the GNC root account if ("ROOT" == gac->type()) { m_rootId = gac->id().toUtf8(); return; } MyMoneyAccount acc; if (m_accountCount == 0) signalProgress(0, m_gncAccountCount, i18n("Loading accounts...")); acc.setName(gac->name()); acc.setDescription(gac->desc()); QDate currentDate = QDate::currentDate(); acc.setOpeningDate(currentDate); acc.setLastModified(currentDate); acc.setLastReconciliationDate(currentDate); if (gac->commodity()->isCurrency()) { acc.setCurrencyId(gac->commodity()->id().toUtf8()); m_currencyCount[gac->commodity()->id()]++; } acc.setParentAccountId(gac->parent().toUtf8()); // now determine the account type and its parent id /* This list taken from # Feb 2006: A RELAX NG Compact schema for gnucash "v2" XML files. # Copyright (C) 2006 Joshua Sled "NO_TYPE" "BANK" "CASH" "CREDIT" "ASSET" "LIABILITY" "STOCK" "MUTUAL" "CURRENCY" "INCOME" "EXPENSE" "EQUITY" "RECEIVABLE" "PAYABLE" "CHECKING" "SAVINGS" "MONEYMRKT" "CREDITLINE" Some don't seem to be used in practice. Not sure what CREDITLINE s/be converted as. */ if ("BANK" == gac->type() || "CHECKING" == gac->type()) { acc.setAccountType(Account::Type::Checkings); } else if ("SAVINGS" == gac->type()) { acc.setAccountType(Account::Type::Savings); } else if ("ASSET" == gac->type()) { acc.setAccountType(Account::Type::Asset); } else if ("CASH" == gac->type()) { acc.setAccountType(Account::Type::Cash); } else if ("CURRENCY" == gac->type()) { acc.setAccountType(Account::Type::Cash); } else if ("STOCK" == gac->type() || "MUTUAL" == gac->type()) { // gnucash allows a 'broker' account to be denominated as type STOCK, but with // a currency balance. We do not need to create a stock account for this // actually, the latest version of gnc (1.8.8) doesn't seem to allow you to do // this any more, though I do have one in my own account... if (gac->commodity()->isCurrency()) { acc.setAccountType(Account::Type::Investment); } else { acc.setAccountType(Account::Type::Stock); } } else if ("EQUITY" == gac->type()) { acc.setAccountType(Account::Type::Equity); } else if ("LIABILITY" == gac->type()) { acc.setAccountType(Account::Type::Liability); } else if ("CREDIT" == gac->type()) { acc.setAccountType(Account::Type::CreditCard); } else if ("INCOME" == gac->type()) { acc.setAccountType(Account::Type::Income); } else if ("EXPENSE" == gac->type()) { acc.setAccountType(Account::Type::Expense); } else if ("RECEIVABLE" == gac->type()) { acc.setAccountType(Account::Type::Asset); } else if ("PAYABLE" == gac->type()) { acc.setAccountType(Account::Type::Liability); } else if ("MONEYMRKT" == gac->type()) { acc.setAccountType(Account::Type::MoneyMarket); } else { // we have here an account type we can't currently handle QString em = i18n("Current importer does not recognize GnuCash account type %1", gac->type()); throw MYMONEYEXCEPTION(em); } // if no parent account is present, assign to one of our standard accounts if ((acc.parentAccountId().isEmpty()) || (acc.parentAccountId() == m_rootId)) { switch (acc.accountGroup()) { case Account::Type::Asset: acc.setParentAccountId(m_storage->asset().id()); break; case Account::Type::Liability: acc.setParentAccountId(m_storage->liability().id()); break; case Account::Type::Income: acc.setParentAccountId(m_storage->income().id()); break; case Account::Type::Expense: acc.setParentAccountId(m_storage->expense().id()); break; case Account::Type::Equity: acc.setParentAccountId(m_storage->equity().id()); break; default: break; // not necessary but avoids compiler warnings } } // extra processing for a stock account if (acc.accountType() == Account::Type::Stock) { // save the id for later linking to investment account m_stockList.append(gac->id()); // set the equity type MyMoneySecurity e = m_storage->security(m_mapEquities[gac->commodity()->id().toUtf8()]); if (gncdebug) qDebug() << "Acct equity search, key =" << gac->commodity()->id() << "found id =" << e.id(); acc.setCurrencyId(e.id()); // actually, the security id if ("MUTUAL" == gac->type()) { e.setSecurityType(Security::Type::MutualFund); if (gncdebug) qDebug() << "Setting" << e.name() << "to mutual"; m_storage->modifySecurity(e); } QString priceSource = gac->getKvpValue("price-source", "string"); if (!priceSource.isEmpty()) getPriceSource(e, priceSource); } if (gac->getKvpValue("tax-related", "integer") == QChar('1')) acc.setValue("Tax", "Yes"); // all the details from the file about the account should be known by now. // calling addAccount will automatically fill in the account ID. m_storage->addAccount(acc); m_mapIds[gac->id().toUtf8()] = acc.id(); // to link gnucash id to ours for tx posting if (gncdebug) qDebug() << "Gnucash account" << gac->id() << "has id of" << acc.id() << ", type of" << MyMoneyAccount::accountTypeToString(acc.accountType()) << "parent is" << acc.parentAccountId(); signalProgress(++m_accountCount, 0); return ; } PASS } //********************************************** convertTransaction ***************************** void MyMoneyGncReader::convertTransaction(const GncTransaction *gtx) { Q_CHECK_PTR(gtx); MyMoneyTransaction tx; MyMoneySplit split; unsigned int i; if (m_transactionCount == 0) signalProgress(0, m_gncTransactionCount, i18n("Loading transactions...")); // initialize class variables related to transactions m_txCommodity = ""; m_txPayeeId = ""; m_potentialTransfer = true; m_splitList.clear(); m_liabilitySplitList.clear(); m_otherSplitList.clear(); // payee, dates, commodity if (!gtx->desc().isEmpty()) m_txPayeeId = createPayee(gtx->desc()); tx.setEntryDate(gtx->dateEntered()); tx.setPostDate(gtx->datePosted()); m_txDatePosted = tx.postDate(); // save for use in splits m_txChequeNo = gtx->no(); // ditto tx.setCommodity(gtx->currency().toUtf8()); m_txCommodity = tx.commodity(); // save in storage, maybe needed for Orphan accounts // process splits for (i = 0; i < gtx->splitCount(); i++) { convertSplit(static_cast(gtx->getSplit(i))); } // handle the odd case of just one split, which gnc allows, // by just duplicating the split // of course, we should change the sign but this case has only ever been seen // when the balance is zero, and can cause kmm to crash, so... if (gtx->splitCount() == 1) { convertSplit(static_cast(gtx->getSplit(0))); } m_splitList += m_liabilitySplitList += m_otherSplitList; // the splits are in order in splitList. Link them to the tx. also, determine the // action type, and fill in some fields which gnc holds at transaction level // first off, is it a transfer (can only have 2 splits?) // also, a tx with just 2 splits is shown by GnuCash as non-split bool nonSplitTx = true; if (m_splitList.count() != 2) { m_potentialTransfer = false; nonSplitTx = false; } QString slotMemo = gtx->getKvpValue(QString("notes")); if (!slotMemo.isEmpty()) tx.setMemo(slotMemo); QList::iterator it = m_splitList.begin(); while (!m_splitList.isEmpty()) { split = *it; // at this point, if m_potentialTransfer is still true, it is actually one! if (m_potentialTransfer) split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)); if ((m_useTxNotes) // if use txnotes option is set && (nonSplitTx) // and it's a (GnuCash) non-split transaction && (!tx.memo().isEmpty())) // and tx notes are present split.setMemo(tx.memo()); // use the tx notes as memo tx.addSplit(split); it = m_splitList.erase(it); } m_storage->addTransaction(tx, true); // all done, add the transaction to storage signalProgress(++m_transactionCount, 0); return ; } //******************************************convertSplit******************************** void MyMoneyGncReader::convertSplit(const GncSplit *gsp) { Q_CHECK_PTR(gsp); MyMoneySplit split; MyMoneyAccount splitAccount; // find the kmm account id corresponding to the gnc id QString kmmAccountId; map_accountIds::const_iterator id = m_mapIds.constFind(gsp->acct().toUtf8()); if (id != m_mapIds.constEnd()) { kmmAccountId = id.value(); } else { // for the case where the acs not found (which shouldn't happen?), create an account with gnc name kmmAccountId = createOrphanAccount(gsp->acct()); } // find the account pointer and save for later splitAccount = m_storage->account(kmmAccountId); // print some data so we can maybe identify this split later // TODO : prints personal data //if (gncdebug) qDebug ("Split data - gncid %s, kmmid %s, memo %s, value %s, recon state %s", // gsp->acct().toLatin1(), kmmAccountId.data(), gsp->memo().toLatin1(), gsp->value().toLatin1(), // gsp->recon().toLatin1()); // payee id split.setPayeeId(m_txPayeeId.toUtf8()); // reconciled state and date switch (gsp->recon().at(0).toLatin1()) { case 'n': split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); break; case 'c': split.setReconcileFlag(eMyMoney::Split::State::Cleared); break; case 'y': split.setReconcileFlag(eMyMoney::Split::State::Reconciled); break; } split.setReconcileDate(gsp->reconDate()); // memo split.setMemo(gsp->memo()); // accountId split.setAccountId(kmmAccountId); // cheque no split.setNumber(m_txChequeNo); // value and quantity MyMoneyMoney splitValue(convBadValue(gsp->value())); if (gsp->value() == "-1/0") { // treat gnc invalid value as zero // it's not quite a consistency check, but easier to treat it as such m_messageList["CC"].append (i18n("Account or Category %1, transaction date %2; split contains invalid value; please check", splitAccount.name(), m_txDatePosted.toString(Qt::ISODate))); } MyMoneyMoney splitQuantity(convBadValue(gsp->qty())); split.setValue(splitValue); // if split currency = tx currency, set shares = value (14/10/05) if (splitAccount.currencyId() == m_txCommodity) { split.setShares(splitValue); } else { split.setShares(splitQuantity); } // in kmm, the first split is important. in this routine we will // save the splits in our split list with the priority: // 1. assets // 2. liabilities // 3. others (categories) // but keeping each in same order as gnucash switch (splitAccount.accountGroup()) { case Account::Type::Asset: if (splitAccount.accountType() == Account::Type::Stock) { split.value().isZero() ? split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::AddShares)) : // free shares? split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares)); m_potentialTransfer = false; // ? // add a price history entry MyMoneySecurity e = m_storage->security(splitAccount.currencyId()); MyMoneyMoney price; if (!split.shares().isZero()) { static const signed64 NEW_DENOM = 10000; price = split.value() / split.shares(); price = MyMoneyMoney(price.toDouble(), NEW_DENOM); } if (!price.isZero()) { TRY { // we can't use m_storage->security coz security list is not built yet m_storage->currency(m_txCommodity); // will throw exception if not currency e.setTradingCurrency(m_txCommodity); if (gncdebug) qDebug() << "added price for" << e.name() << price.toString() << "date" << m_txDatePosted.toString(Qt::ISODate); m_storage->modifySecurity(e); MyMoneyPrice dealPrice(e.id(), m_txCommodity, m_txDatePosted, price, i18n("Imported Transaction")); m_storage->addPrice(dealPrice); } CATCH { // stock transfer; treat like free shares? split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::AddShares)); } } } else { // not stock if (split.value().isNegative()) { bool isNumeric = false; if (!split.number().isEmpty()) { split.number().toLong(&isNumeric); // No QString.isNumeric()?? } if (isNumeric) { split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Check)); } else { split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal)); } } else { split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)); } } m_splitList.append(split); break; case Account::Type::Liability: split.value().isNegative() ? split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal)) : split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)); m_liabilitySplitList.append(split); break; default: m_potentialTransfer = false; m_otherSplitList.append(split); } // backdate the account opening date if necessary if (m_txDatePosted < splitAccount.openingDate()) { splitAccount.setOpeningDate(m_txDatePosted); m_storage->modifyAccount(splitAccount); } return ; } //********************************* convertTemplateTransaction ********************************************** MyMoneyTransaction MyMoneyGncReader::convertTemplateTransaction(const QString& schedName, const GncTransaction *gtx) { Q_CHECK_PTR(gtx); MyMoneyTransaction tx; MyMoneySplit split; unsigned int i; if (m_templateCount == 0) signalProgress(0, 1, i18n("Loading templates...")); // initialize class variables related to transactions m_txCommodity = ""; m_txPayeeId = ""; m_potentialTransfer = true; m_splitList.clear(); m_liabilitySplitList.clear(); m_otherSplitList.clear(); // payee, dates, commodity if (!gtx->desc().isEmpty()) { m_txPayeeId = createPayee(gtx->desc()); } else { m_txPayeeId = createPayee(i18n("Unknown payee")); // schedules require a payee tho normal tx's don't. not sure why... } tx.setEntryDate(gtx->dateEntered()); tx.setPostDate(gtx->datePosted()); m_txDatePosted = tx.postDate(); tx.setCommodity(gtx->currency().toUtf8()); m_txCommodity = tx.commodity(); // save for possible use in orphan account // process splits for (i = 0; i < gtx->splitCount(); i++) { convertTemplateSplit(schedName, static_cast(gtx->getSplit(i))); } // determine the action type for the splits and link them to the template tx if (!m_otherSplitList.isEmpty()) m_potentialTransfer = false; // tfrs can occur only between assets and asset/liabilities m_splitList += m_liabilitySplitList += m_otherSplitList; // the splits are in order in splitList. Transfer them to the tx // also, determine the action type. first off, is it a transfer (can only have 2 splits?) if (m_splitList.count() != 2) m_potentialTransfer = false; // at this point, if m_potentialTransfer is still true, it is actually one! QString txMemo = ""; QList::iterator it = m_splitList.begin(); while (!m_splitList.isEmpty()) { split = *it; if (m_potentialTransfer) { split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)); } else { if (split.value().isNegative()) { //split.setAction (negativeActionType); split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal)); } else { //split.setAction (positiveActionType); split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)); } } split.setNumber(gtx->no()); // set cheque no (or equivalent description) // Arbitrarily, save the first non-null split memo as the memo for the whole tx // I think this is necessary because txs with just 2 splits (the majority) // are not viewable as split transactions in kmm so the split memo is not seen if ((txMemo.isEmpty()) && (!split.memo().isEmpty())) txMemo = split.memo(); tx.addSplit(split); it = m_splitList.erase(it); } // memo - set from split tx.setMemo(txMemo); signalProgress(++m_templateCount, 0); return (tx); } //********************************* convertTemplateSplit **************************************************** void MyMoneyGncReader::convertTemplateSplit(const QString& schedName, const GncTemplateSplit *gsp) { Q_CHECK_PTR(gsp); // convertTemplateSplit MyMoneySplit split; MyMoneyAccount splitAccount; unsigned int i, j; bool nonNumericFormula = false; // action, value and account will be set from slots // reconcile state, always Not since it hasn't even been posted yet (?) split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); // memo split.setMemo(gsp->memo()); // payee id split.setPayeeId(m_txPayeeId.toUtf8()); // read split slots (KVPs) int xactionCount = 0; int validSlotCount = 0; QString gncAccountId; for (i = 0; i < gsp->kvpCount(); i++) { const GncKvp& slot = gsp->getKvp(i); if ((slot.key() == "sched-xaction") && (slot.type() == "frame")) { bool bFoundStringCreditFormula = false; bool bFoundStringDebitFormula = false; bool bFoundGuidAccountId = false; QString gncCreditFormula, gncDebitFormula; for (j = 0; j < slot.kvpCount(); j++) { const GncKvp& subSlot = slot.getKvp(j); // again, see comments above. when we have a full specification // of all the options available to us, we can no doubt improve on this if ((subSlot.key() == "credit-formula") && (subSlot.type() == "string")) { gncCreditFormula = subSlot.value(); bFoundStringCreditFormula = true; } if ((subSlot.key() == "debit-formula") && (subSlot.type() == "string")) { gncDebitFormula = subSlot.value(); bFoundStringDebitFormula = true; } if ((subSlot.key() == "account") && (subSlot.type() == "guid")) { gncAccountId = subSlot.value(); bFoundGuidAccountId = true; } } // all data read, now check we have everything if ((bFoundStringCreditFormula) && (bFoundStringDebitFormula) && (bFoundGuidAccountId)) { if (gncdebug) qDebug() << "Found valid slot; credit" << gncCreditFormula << "debit" << gncDebitFormula << "acct" << gncAccountId; validSlotCount++; } // validate numeric, work out sign MyMoneyMoney exFormula; exFormula.setNegativeMonetarySignPosition(eMyMoney::Money::BeforeQuantityMoney); QString numericTest; char crdr = 0 ; if (!gncCreditFormula.isEmpty()) { crdr = 'C'; numericTest = gncCreditFormula; } else if (!gncDebitFormula.isEmpty()) { crdr = 'D'; numericTest = gncDebitFormula; } KMyMoneyMoneyValidator v(0); int pos; // useless, but required for validator if (v.validate(numericTest, pos) == QValidator::Acceptable) { switch (crdr) { case 'C': exFormula = QString("-" + numericTest); break; case 'D': exFormula = numericTest; } } else { if (gncdebug) qDebug() << numericTest << "is not numeric"; nonNumericFormula = true; } split.setValue(exFormula); xactionCount++; } else { m_messageList["SC"].append( i18n("Schedule %1 contains unknown action (key = %2, type = %3)", schedName, slot.key(), slot.type())); m_suspectSchedule = true; } } // report this as untranslatable tx if (xactionCount > 1) { m_messageList["SC"].append( i18n("Schedule %1 contains multiple actions; only one has been imported", schedName)); m_suspectSchedule = true; } if (validSlotCount == 0) { m_messageList["SC"].append( i18n("Schedule %1 contains no valid splits", schedName)); m_suspectSchedule = true; } if (nonNumericFormula) { m_messageList["SC"].append( i18n("Schedule %1 appears to contain a formula. GnuCash formulae are not convertible", schedName)); m_suspectSchedule = true; } // find the kmm account id corresponding to the gnc id QString kmmAccountId; map_accountIds::const_iterator id = m_mapIds.constFind(gncAccountId.toUtf8()); if (id != m_mapIds.constEnd()) { kmmAccountId = id.value(); } else { // for the case where the acs not found (which shouldn't happen?), create an account with gnc name kmmAccountId = createOrphanAccount(gncAccountId); } splitAccount = m_storage->account(kmmAccountId); split.setAccountId(kmmAccountId); // if split currency = tx currency, set shares = value (14/10/05) if (splitAccount.currencyId() == m_txCommodity) { split.setShares(split.value()); } /* else { //FIXME: scheduled currency or investment tx needs to be investigated split.setShares (splitQuantity); } */ // add the split to one of the lists switch (splitAccount.accountGroup()) { case Account::Type::Asset: m_splitList.append(split); break; case Account::Type::Liability: m_liabilitySplitList.append(split); break; default: m_otherSplitList.append(split); } // backdate the account opening date if necessary if (m_txDatePosted < splitAccount.openingDate()) { splitAccount.setOpeningDate(m_txDatePosted); m_storage->modifyAccount(splitAccount); } return ; } //********************************* convertSchedule ******************************************************** void MyMoneyGncReader::convertSchedule(const GncSchedule *gsc) { TRY { Q_CHECK_PTR(gsc); MyMoneySchedule sc; MyMoneyTransaction tx; m_suspectSchedule = false; QDate startDate, nextDate, lastDate, endDate; // for date calculations QDate today = QDate::currentDate(); int numOccurs, remOccurs; if (m_scheduleCount == 0) signalProgress(0, m_gncScheduleCount, i18n("Loading schedules...")); // schedule name sc.setName(gsc->name()); // find the transaction template as stored earlier QList::const_iterator itt; for (itt = m_templateList.constBegin(); itt != m_templateList.constEnd(); ++itt) { // the id to match against is the split:account value in the splits if (static_cast((*itt)->getSplit(0))->acct() == gsc->templId()) break; } if (itt == m_templateList.constEnd()) { throw MYMONEYEXCEPTION(i18n("Cannot find template transaction for schedule %1", sc.name())); } else { tx = convertTemplateTransaction(sc.name(), *itt); } tx.clearId(); // define the conversion table for intervals struct convIntvl { QString gncType; // the gnucash name unsigned char interval; // for date calculation unsigned int intervalCount; Schedule::Occurrence occ; // equivalent occurrence code Schedule::WeekendOption wo; }; /* other intervals supported by gnc according to Josh Sled's schema (see above) "none" "semi_monthly" */ /* some of these type names do not appear in gnucash and are difficult to generate for pre 2.2 files.They can be generated for 2.2 however, by GncRecurrence::getFrequency() */ static convIntvl vi [] = { {"once", 'o', 1, Schedule::Occurrence::Once, Schedule::WeekendOption::MoveNothing }, {"daily" , 'd', 1, Schedule::Occurrence::Daily, Schedule::WeekendOption::MoveNothing }, //{"daily_mf", 'd', 1, Schedule::Occurrence::Daily, Schedule::WeekendOption::MoveAfter }, doesn't work, need new freq in kmm {"30-days" , 'd', 30, Schedule::Occurrence::EveryThirtyDays, Schedule::WeekendOption::MoveNothing }, {"weekly", 'w', 1, Schedule::Occurrence::Weekly, Schedule::WeekendOption::MoveNothing }, {"bi_weekly", 'w', 2, Schedule::Occurrence::EveryOtherWeek, Schedule::WeekendOption::MoveNothing }, {"three-weekly", 'w', 3, Schedule::Occurrence::EveryThreeWeeks, Schedule::WeekendOption::MoveNothing }, {"four-weekly", 'w', 4, Schedule::Occurrence::EveryFourWeeks, Schedule::WeekendOption::MoveNothing }, {"eight-weekly", 'w', 8, Schedule::Occurrence::EveryEightWeeks, Schedule::WeekendOption::MoveNothing }, {"monthly", 'm', 1, Schedule::Occurrence::Monthly, Schedule::WeekendOption::MoveNothing }, {"two-monthly", 'm', 2, Schedule::Occurrence::EveryOtherMonth, Schedule::WeekendOption::MoveNothing }, {"quarterly", 'm', 3, Schedule::Occurrence::Quarterly, Schedule::WeekendOption::MoveNothing }, {"tri_annually", 'm', 4, Schedule::Occurrence::EveryFourMonths, Schedule::WeekendOption::MoveNothing }, {"semi_yearly", 'm', 6, Schedule::Occurrence::TwiceYearly, Schedule::WeekendOption::MoveNothing }, {"yearly", 'y', 1, Schedule::Occurrence::Yearly, Schedule::WeekendOption::MoveNothing }, {"two-yearly", 'y', 2, Schedule::Occurrence::EveryOtherYear, Schedule::WeekendOption::MoveNothing }, {"zzz", 'y', 1, Schedule::Occurrence::Yearly, Schedule::WeekendOption::MoveNothing} // zzz = stopper, may cause problems. what else can we do? }; QString frequency = "unknown"; // set default to unknown frequency bool unknownOccurs = false; // may have zero, or more than one frequency/recurrence spec QString schedEnabled; if (gsc->version() == "2.0.0") { if (gsc->m_vpRecurrence.count() != 1) { unknownOccurs = true; } else { const GncRecurrence *gre = gsc->m_vpRecurrence.first(); //qDebug (QString("Sched %1, pt %2, mu %3, sd %4").arg(gsc->name()).arg(gre->periodType()) // .arg(gre->mult()).arg(gre->startDate().toString(Qt::ISODate))); frequency = gre->getFrequency(); schedEnabled = gsc->enabled(); } sc.setOccurrence(Schedule::Occurrence::Once); // FIXME - how to convert } else { // find this interval const GncFreqSpec *fs = gsc->getFreqSpec(); if (fs == 0) { unknownOccurs = true; } else { frequency = fs->intervalType(); if (!fs->m_fsList.isEmpty()) unknownOccurs = true; // nested freqspec } schedEnabled = 'y'; // earlier versions did not have an enable flag } int i; for (i = 0; vi[i].gncType != "zzz"; i++) { if (frequency == vi[i].gncType) break; } if (vi[i].gncType == "zzz") { m_messageList["SC"].append( i18n("Schedule %1 has interval of %2 which is not currently available", sc.name(), frequency)); i = 0; // treat as single occurrence m_suspectSchedule = true; } if (unknownOccurs) { m_messageList["SC"].append( i18n("Schedule %1 contains unknown interval specification; please check for correct operation", sc.name())); m_suspectSchedule = true; } // set the occurrence interval, weekend option, start date sc.setOccurrence(vi[i].occ); sc.setWeekendOption(vi[i].wo); sc.setStartDate(gsc->startDate()); // if a last date was specified, use it, otherwise try to work out the last date sc.setLastPayment(gsc->lastDate()); numOccurs = gsc->numOccurs().toInt(); if (sc.lastPayment() == QDate()) { nextDate = lastDate = gsc->startDate(); while ((nextDate < today) && (numOccurs-- != 0)) { lastDate = nextDate; nextDate = incrDate(lastDate, vi[i].interval, vi[i].intervalCount); } sc.setLastPayment(lastDate); } // under Tom's new regime, the tx dates are the next due date (I think) tx.setPostDate(incrDate(sc.lastPayment(), vi[i].interval, vi[i].intervalCount)); tx.setEntryDate(incrDate(sc.lastPayment(), vi[i].interval, vi[i].intervalCount)); // if an end date was specified, use it, otherwise if the input file had a number // of occurs remaining, work out the end date sc.setEndDate(gsc->endDate()); numOccurs = gsc->numOccurs().toInt(); remOccurs = gsc->remOccurs().toInt(); if ((sc.endDate() == QDate()) && (remOccurs > 0)) { endDate = sc.lastPayment(); while (remOccurs-- > 0) { endDate = incrDate(endDate, vi[i].interval, vi[i].intervalCount); } sc.setEndDate(endDate); } // Check for sched deferred interval. Don't know how/if we can handle it, or even what it means... if (gsc->getSchedDef() != 0) { m_messageList["SC"].append( i18n("Schedule %1 contains a deferred interval specification; please check for correct operation", sc.name())); m_suspectSchedule = true; } // payment type, options sc.setPaymentType((Schedule::PaymentType)Schedule::PaymentType::Other); sc.setFixed(!m_suspectSchedule); // if any probs were found, set it as variable so user will always be prompted // we don't currently have a 'disable' option, but just make sure auto-enter is off if not enabled //qDebug(QString("%1 and %2").arg(gsc->autoCreate()).arg(schedEnabled)); sc.setAutoEnter((gsc->autoCreate() == QChar('y')) && (schedEnabled == QChar('y'))); //qDebug(QString("autoEnter set to %1").arg(sc.autoEnter())); // type QString actionType = tx.splits().first().action(); if (actionType == MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit)) { sc.setType((Schedule::Type)Schedule::Type::Deposit); } else if (actionType == MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) { sc.setType((Schedule::Type)Schedule::Type::Transfer); } else { sc.setType((Schedule::Type)Schedule::Type::Bill); } // finally, set the transaction pointer sc.setTransaction(tx); //tell the storage objects we have a new schedule object. if (m_suspectSchedule && m_dropSuspectSchedules) { m_messageList["SC"].append( i18n("Schedule %1 dropped at user request", sc.name())); } else { m_storage->addSchedule(sc); if (m_suspectSchedule) m_suspectList.append(sc.id()); } signalProgress(++m_scheduleCount, 0); return ; } PASS } //********************************* convertFreqSpec ******************************************************** void MyMoneyGncReader::convertFreqSpec(const GncFreqSpec *) { // Nowt to do here at the moment, convertSched only retrieves the interval type // but we will probably need to look into the nested freqspec when we properly implement semi-monthly and stuff return ; } //********************************* convertRecurrence ******************************************************** void MyMoneyGncReader::convertRecurrence(const GncRecurrence *) { return ; } //********************************************************************************************************** //************************************* terminate ********************************************************** void MyMoneyGncReader::terminate() { TRY { // All data has been converted and added to storage // this code is just temporary to show us what is in the file. if (gncdebug) qDebug("%d accounts found in the GnuCash file", (unsigned int)m_mapIds.count()); for (map_accountIds::const_iterator it = m_mapIds.constBegin(); it != m_mapIds.constEnd(); ++it) { if (gncdebug) qDebug() << "key =" << it.key() << "value =" << it.value(); } // first step is to implement the users investment option, now we // have all the accounts available QList::iterator stocks; for (stocks = m_stockList.begin(); stocks != m_stockList.end(); ++stocks) { checkInvestmentOption(*stocks); } // Next step is to walk the list and assign the parent/child relationship between the objects. unsigned int i = 0; signalProgress(0, m_accountCount, i18n("Reorganizing accounts...")); QList list; QList::iterator acc; m_storage->accountList(list); for (acc = list.begin(); acc != list.end(); ++acc) { if ((*acc).parentAccountId() == m_storage->asset().id()) { MyMoneyAccount assets = m_storage->asset(); m_storage->addAccount(assets, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main asset account"; } else if ((*acc).parentAccountId() == m_storage->liability().id()) { MyMoneyAccount liabilities = m_storage->liability(); m_storage->addAccount(liabilities, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main liability account"; } else if ((*acc).parentAccountId() == m_storage->income().id()) { MyMoneyAccount incomes = m_storage->income(); m_storage->addAccount(incomes, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main income account"; } else if ((*acc).parentAccountId() == m_storage->expense().id()) { MyMoneyAccount expenses = m_storage->expense(); m_storage->addAccount(expenses, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main expense account"; } else if ((*acc).parentAccountId() == m_storage->equity().id()) { MyMoneyAccount equity = m_storage->equity(); m_storage->addAccount(equity, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main equity account"; } else if ((*acc).parentAccountId() == m_rootId) { if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main root account"; } else { // it is not under one of the main accounts, so find gnucash parent QString parentKey = (*acc).parentAccountId(); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of " << (*acc).parentAccountId(); map_accountIds::const_iterator id = m_mapIds.constFind(parentKey); if (id != m_mapIds.constEnd()) { if (gncdebug) qDebug() << "Setting account id" << (*acc).id() << "parent account id to" << id.value(); MyMoneyAccount parent = m_storage->account(id.value()); parent = checkConsistency(parent, (*acc)); m_storage->addAccount(parent, (*acc)); } else { throw MYMONEYEXCEPTION("terminate() could not find account id"); } } signalProgress(++i, 0); } // end for account signalProgress(0, 1, (".")); // debug - get rid of reorg message // offer the most common account currency as a default QString mainCurrency = ""; unsigned int maxCount = 0; QMap::ConstIterator it; for (it = m_currencyCount.constBegin(); it != m_currencyCount.constEnd(); ++it) { if (it.value() > maxCount) { maxCount = it.value(); mainCurrency = it.key(); } } if (mainCurrency != "") { QString question = i18n("Your main currency seems to be %1 (%2); do you want to set this as your base currency?", mainCurrency, m_storage->currency(mainCurrency.toUtf8()).name()); if (KMessageBox::questionYesNo(0, question, PACKAGE) == KMessageBox::Yes) { m_storage->setValue("kmm-baseCurrency", mainCurrency); } } // now produce the end of job reports - first, work out which ones are required QList sectionsToReport; // list of sections needing report sectionsToReport.append("MN"); // always build the main section if ((m_ccCount = m_messageList["CC"].count()) > 0) sectionsToReport.append("CC"); if ((m_orCount = m_messageList["OR"].count()) > 0) sectionsToReport.append("OR"); if ((m_scCount = m_messageList["SC"].count()) > 0) sectionsToReport.append("SC"); // produce the sections in separate message boxes bool exit = false; int si; for (si = 0; (si < sectionsToReport.count()) && !exit; ++si) { QString button0Text = i18nc("Button to show more detailed data", "More"); if (si + 1 == sectionsToReport.count()) button0Text = i18nc("Button to close the current dialog", "Done"); // last section KGuiItem yesItem(button0Text, QIcon(), "", ""); KGuiItem noItem(i18n("Save Report"), QIcon(), "", ""); switch (KMessageBox::questionYesNoCancel(0, buildReportSection(sectionsToReport[si]), PACKAGE, yesItem, noItem)) { case KMessageBox::Yes: break; case KMessageBox::No: exit = writeReportToFile(sectionsToReport); break; default: exit = true; break; } } for (si = 0; si < m_suspectList.count(); ++si) { auto sc = m_storage->schedule(m_suspectList[si]); KMessageBox::information(0, i18n("Problems were encountered in converting schedule '%1'.", sc.name()), PACKAGE); // TODO: return this feature // switch (KMessageBox::warningYesNo(0, i18n("Problems were encountered in converting schedule '%1'.\nDo you want to review or edit it now?", sc.name()), PACKAGE)) { // case KMessageBox::Yes: // auto s = new KEditScheduleDlg(sc); // if (s->exec()) // m_storage->modifySchedule(s->schedule()); // delete s; // break; // default: // break; // } } } PASS } //************************************ buildReportSection************************************ QString MyMoneyGncReader::buildReportSection(const QString& source) { TRY { QString s = ""; bool more = false; if (source == "MN") { s.append(i18n("Found:\n\n")); s.append(i18np("%1 commodity (equity)\n", "%1 commodities (equities)\n", m_commodityCount)); s.append(i18np("%1 price\n", "%1 prices\n", m_priceCount)); s.append(i18np("%1 account\n", "%1 accounts\n", m_accountCount)); s.append(i18np("%1 transaction\n", "%1 transactions\n", m_transactionCount)); s.append(i18np("%1 schedule\n", "%1 schedules\n", m_scheduleCount)); s.append("\n\n"); if (m_ccCount == 0) { s.append(i18n("No inconsistencies were detected\n")); } else { s.append(i18np("%1 inconsistency was detected and corrected\n", "%1 inconsistencies were detected and corrected\n", m_ccCount)); more = true; } if (m_orCount > 0) { s.append("\n\n"); s.append(i18np("%1 orphan account was created\n", "%1 orphan accounts were created\n", m_orCount)); more = true; } if (m_scCount > 0) { s.append("\n\n"); s.append(i18np("%1 possible schedule problem was noted\n", "%1 possible schedule problems were noted\n", m_scCount)); more = true; } QString unsupported(""); QString lineSep("\n - "); if (m_smallBusinessFound) unsupported.append(lineSep + i18n("Small Business Features (Customers, Invoices, etc.)")); if (m_budgetsFound) unsupported.append(lineSep + i18n("Budgets")); if (m_lotsFound) unsupported.append(lineSep + i18n("Lots")); if (!unsupported.isEmpty()) { unsupported.prepend(i18n("The following features found in your file are not currently supported:")); s.append(unsupported); } if (more) s.append(i18n("\n\nPress More for further information")); } else { s = m_messageList[source].join(QChar('\n')); } if (gncdebug) qDebug() << s; return (static_cast(s)); } PASS } //************************ writeReportToFile********************************* bool MyMoneyGncReader::writeReportToFile(const QList& sectionsToReport) { TRY { int i; QString fd = QFileDialog::getSaveFileName(0, QString(), QString(), i18n("Save report as")); if (fd.isEmpty()) return (false); QFile reportFile(fd); if (!reportFile.open(QIODevice::WriteOnly)) { return (false); } QTextStream stream(&reportFile); for (i = 0; i < sectionsToReport.count(); i++) stream << buildReportSection(sectionsToReport[i]) << endl; reportFile.close(); return (true); } PASS } /**************************************************************************** Utility routines *****************************************************************************/ //************************ createPayee *************************** QString MyMoneyGncReader::createPayee(const QString& gncDescription) { MyMoneyPayee payee; TRY { payee = m_storage->payeeByName(gncDescription); } CATCH { // payee not found, create one payee.setName(gncDescription); m_storage->addPayee(payee); } return (payee.id()); } //************************************** createOrphanAccount ******************************* QString MyMoneyGncReader::createOrphanAccount(const QString& gncName) { MyMoneyAccount acc; acc.setName("orphan_" + gncName); acc.setDescription(i18n("Orphan created from unknown GnuCash account")); QDate today = QDate::currentDate(); acc.setOpeningDate(today); acc.setLastModified(today); acc.setLastReconciliationDate(today); acc.setCurrencyId(m_txCommodity); acc.setAccountType(Account::Type::Asset); acc.setParentAccountId(m_storage->asset().id()); m_storage->addAccount(acc); // assign the gnucash id as the key into the map to find our id m_mapIds[gncName.toUtf8()] = acc.id(); m_messageList["OR"].append( i18n("One or more transactions contain a reference to an otherwise unknown account\n" "An asset account with the name %1 has been created to hold the data", acc.name())); return (acc.id()); } //****************************** incrDate ********************************************* QDate MyMoneyGncReader::incrDate(QDate lastDate, unsigned char interval, unsigned int intervalCount) { TRY { switch (interval) { case 'd': return (lastDate.addDays(intervalCount)); case 'w': return (lastDate.addDays(intervalCount * 7)); case 'm': return (lastDate.addMonths(intervalCount)); case 'y': return (lastDate.addYears(intervalCount)); case 'o': // once-only return (lastDate); } throw MYMONEYEXCEPTION(i18n("Internal error - invalid interval char in incrDate")); QDate r = QDate(); return (r); // to keep compiler happy } PASS } //********************************* checkConsistency ********************************** MyMoneyAccount MyMoneyGncReader::checkConsistency(MyMoneyAccount& parent, MyMoneyAccount& child) { TRY { // gnucash is flexible/weird enough to allow various inconsistencies // these are a couple I found in my file, no doubt more will be discovered if ((child.accountType() == Account::Type::Investment) && (parent.accountType() != Account::Type::Asset)) { m_messageList["CC"].append( i18n("An Investment account must be a child of an Asset account\n" "Account %1 will be stored under the main Asset account", child.name())); return m_storage->asset(); } if ((child.accountType() == Account::Type::Income) && (parent.accountType() != Account::Type::Income)) { m_messageList["CC"].append( i18n("An Income account must be a child of an Income account\n" "Account %1 will be stored under the main Income account", child.name())); return m_storage->income(); } if ((child.accountType() == Account::Type::Expense) && (parent.accountType() != Account::Type::Expense)) { m_messageList["CC"].append( i18n("An Expense account must be a child of an Expense account\n" "Account %1 will be stored under the main Expense account", child.name())); return m_storage->expense(); } return (parent); } PASS } //*********************************** checkInvestmentOption ************************* void MyMoneyGncReader::checkInvestmentOption(QString stockId) { // implement the investment option for stock accounts // first check whether the parent account (gnucash id) is actually an // investment account. if it is, no further action is needed MyMoneyAccount stockAcc = m_storage->account(m_mapIds[stockId.toUtf8()]); MyMoneyAccount parent; QString parentKey = stockAcc.parentAccountId(); map_accountIds::const_iterator id = m_mapIds.constFind(parentKey); if (id != m_mapIds.constEnd()) { parent = m_storage->account(id.value()); if (parent.accountType() == Account::Type::Investment) return ; } // so now, check the investment option requested by the user // option 0 creates a separate investment account for each stock account if (m_investmentOption == 0) { MyMoneyAccount invAcc(stockAcc); invAcc.setAccountType(Account::Type::Investment); invAcc.setCurrencyId(QString("")); // we don't know what currency it is!! invAcc.setParentAccountId(parentKey); // intersperse it between old parent and child stock acct m_storage->addAccount(invAcc); m_mapIds [invAcc.id()] = invAcc.id(); // so stock account gets parented (again) to investment account later if (gncdebug) qDebug() << "Created investment account" << invAcc.name() << "as id" << invAcc.id() << "parent" << invAcc.parentAccountId(); if (gncdebug) qDebug() << "Setting stock" << stockAcc.name() << "id" << stockAcc.id() << "as child of" << invAcc.id(); stockAcc.setParentAccountId(invAcc.id()); m_storage->addAccount(invAcc, stockAcc); // investment option 1 creates a single investment account for all stocks } else if (m_investmentOption == 1) { static QString singleInvAccId = ""; MyMoneyAccount singleInvAcc; bool ok = false; if (singleInvAccId.isEmpty()) { // if the account has not yet been created QString invAccName; while (!ok) { invAccName = QInputDialog::getText(0, QStringLiteral(PACKAGE), i18n("Enter the investment account name "), QLineEdit::Normal, i18n("My Investments"), &ok); } singleInvAcc.setName(invAccName); singleInvAcc.setAccountType(Account::Type::Investment); singleInvAcc.setCurrencyId(QString("")); singleInvAcc.setParentAccountId(m_storage->asset().id()); m_storage->addAccount(singleInvAcc); m_mapIds [singleInvAcc.id()] = singleInvAcc.id(); // so stock account gets parented (again) to investment account later if (gncdebug) qDebug() << "Created investment account" << singleInvAcc.name() << "as id" << singleInvAcc.id() << "parent" << singleInvAcc.parentAccountId() << "reparenting stock"; singleInvAccId = singleInvAcc.id(); } else { // the account has already been created singleInvAcc = m_storage->account(singleInvAccId); } m_storage->addAccount(singleInvAcc, stockAcc); // add stock as child // the original intention of option 2 was to allow any asset account to be converted to an investment (broker) account // however, since we have already stored the accounts as asset, we have no way at present of changing their type // the only alternative would be to hold all the gnucash data in memory, then implement this option, then convert all the data // that would mean a major overhaul of the code. Perhaps I'll think of another way... } else if (m_investmentOption == 2) { static int lastSelected = 0; MyMoneyAccount invAcc(stockAcc); QStringList accList; QList list; QList::iterator acc; m_storage->accountList(list); // build a list of candidates for the input box for (acc = list.begin(); acc != list.end(); ++acc) { // if (((*acc).accountGroup() == Account::Type::Asset) && ((*acc).accountType() != Account::Type::Stock)) accList.append ((*acc).name()); if ((*acc).accountType() == Account::Type::Investment) accList.append((*acc).name()); } //if (accList.isEmpty()) qWarning ("No available accounts"); bool ok = false; while (!ok) { // keep going till we have a valid investment parent QString invAccName = QInputDialog::getItem(0, PACKAGE, i18n("Select parent investment account or enter new name. Stock %1", stockAcc.name()), accList, lastSelected, true, &ok); if (ok) { lastSelected = accList.indexOf(invAccName); // preserve selection for next time for (acc = list.begin(); acc != list.end(); ++acc) { if ((*acc).name() == invAccName) break; } if (acc != list.end()) { // an account was selected invAcc = *acc; } else { // a new account name was entered invAcc.setAccountType(Account::Type::Investment); invAcc.setName(invAccName); invAcc.setCurrencyId(QString("")); invAcc.setParentAccountId(m_storage->asset().id()); m_storage->addAccount(invAcc); ok = true; } if (invAcc.accountType() == Account::Type::Investment) { ok = true; } else { // this code is probably not going to be implemented coz we can't change account types (??) #if 0 QMessageBox mb(PACKAGE, i18n("%1 is not an Investment Account. Do you wish to make it one?", invAcc.name()), QMessageBox::Question, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, Qt::NoButton); switch (mb.exec()) { case QMessageBox::No : ok = false; break; default: // convert it - but what if it has splits??? qWarning("Not yet implemented"); ok = true; break; } #endif switch (KMessageBox::questionYesNo(0, i18n("%1 is not an Investment Account. Do you wish to make it one?", invAcc.name()), PACKAGE)) { case KMessageBox::Yes: // convert it - but what if it has splits??? qWarning("Not yet implemented"); ok = true; break; default: ok = false; break; } } } // end if ok - user pressed Cancel } // end while !ok m_mapIds [invAcc.id()] = invAcc.id(); // so stock account gets parented (again) to investment account later m_storage->addAccount(invAcc, stockAcc); } else { // investment option != 0, 1, 2 qWarning("Invalid investment option %d", m_investmentOption); } } // get the price source for a stock (gnc account) where online quotes are requested void MyMoneyGncReader::getPriceSource(MyMoneySecurity stock, QString gncSource) { // if he wants to use Finance::Quote, no conversion of source name is needed if (m_useFinanceQuote) { stock.setValue("kmm-online-quote-system", "Finance::Quote"); stock.setValue("kmm-online-source", gncSource.toLower()); m_storage->modifySecurity(stock); return; } // first check if we have already asked about this source // (mapSources is initialy empty. We may be able to pre-fill it with some equivalent // sources, if such things do exist. User feedback may help here.) QMap::const_iterator it; for (it = m_mapSources.constBegin(); it != m_mapSources.constEnd(); ++it) { if (it.key() == gncSource) { stock.setValue("kmm-online-source", it.value()); m_storage->modifySecurity(stock); return; } } // not found in map, so ask the user QPointer dlg = new KGncPriceSourceDlg(stock.name(), gncSource); dlg->exec(); QString s = dlg->selectedSource(); if (!s.isEmpty()) { stock.setValue("kmm-online-source", s); m_storage->modifySecurity(stock); } if (dlg->alwaysUse()) m_mapSources[gncSource] = s; delete dlg; return; } // functions to control the progress bar //*********************** setProgressCallback ***************************** void MyMoneyGncReader::setProgressCallback(void(*callback)(int, int, const QString&)) { m_progressCallback = callback; return ; } //************************** signalProgress ******************************* void MyMoneyGncReader::signalProgress(int current, int total, const QString& msg) { if (m_progressCallback != 0) (*m_progressCallback)(current, total, msg); return ; } #endif // _GNCFILEANON diff --git a/kmymoney/plugins/gnc/import/mymoneygncreader.h b/kmymoney/plugins/gnc/import/mymoneygncreader.h index 3b326591a..78e7bb0df 100644 --- a/kmymoney/plugins/gnc/import/mymoneygncreader.h +++ b/kmymoney/plugins/gnc/import/mymoneygncreader.h @@ -1,1088 +1,1087 @@ /*************************************************************************** mymoneygncreader - description ------------------- begin : Wed Mar 3 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /* The main class of this module, MyMoneyGncReader, contains only a readFile() function, which controls the import of data from an XML file created by the current GnuCash version (1.8.8). The XML is processed in class XmlReader, which is an implementation of the Qt SAX2 reader class. Data in the input file is processed as a set of objects which fortunately, though perhaps not surprisingly, have almost a one-for-one correspondence with KMyMoney objects. These objects are bounded by start and end XML elements, and may contain both nested objects (described as sub objects in the code), and data items, also delimited by start and end elements. For example: * start of sub object within file Account Name * data string with start and end elements ... * end of sub objects A GnuCash file may consist of more than one 'book', or set of data. It is not clear how we could currently implement this, so only the first book in a file is processed. This should satisfy most user situations. GnuCash is somewhat inconsistent in its division of the major sections of the file. For example, multiple price history entries are delimited by elements, while each account starts with its own top-level element. In general, the 'container' elements are ignored. XmlReader This is an implementation of the Qt QXmlDefaultHandler class, which provides three main function calls in addition to start and end of document. The startElement() and endElement() calls are self-explanatory, the characters() function provides data strings. Thus in the above example, the sequence of calls would be startElement() for gnc:account startElement() for act:name characters() for 'Account Name' endElement() for act:name ... endElement() for gnc:account Objects Since the processing requirements of XML for most elements are very similar, the common code is implemented in a GncObject class, from which the others are derived, with virtual function calls to cater for any differences. The 'grandfather' object, GncFile representing the file (or more correctly, 'book') as a whole, is created in the startDocument() function call. The constructor function of each object is responsible for providing two lists for the XmlReader to scan, a list of element names which represent sub objects (called sub elements in the code), and a similar list of names representing data elements. In addition, an array of variables (m_v) is provided and initialized, to contain the actual data strings. Implementation Since objects may be nested, a stack is used, with the top element pointing to the 'current object'. The startDocument() call creates the first, GncFile, object at the top of the stack. As each startElement() call occurs, the two#include "mymoneygncreader.h" element lists created by the current object are scanned. If this element represents the start of a sub object, the current object's subEl() function is called to create an instance of the appropriate type. This is then pushed to the top of the stack, and the new object's initiate() function is called. This is used to process any XML attributes attached to the element; GnuCash makes little use of these. If this represents the start of a data element, a pointer (m_dataPointer) is set to point to an entry in the array (m_v) in which a subsequent characters() call can store the actual data. When an endElement() call occurs, a check is made to see if it matches the element name which started the current object. If so, the object's terminate() function is called. If the object represents a similar KMM object, this will normally result in a call to a conversion routine in the main (MyMoneyGncReader) class to convert the data to native format and place it in storage. The stack is then popped, and the parent (now current) object notified by a call to its endSubEl() function. Again depending on the type of object, this will either delete the instance, or save it in its own storage for later processing. For example, a GncSplit object makes little sense outside the context of its transaction, so will be saved by the transaction. A GncTransaction object on the other hand will be converted, along with its attendant splits, and then deleted by its parent. Since at any one time an object will only be processing either a subobject or a data element, a single object variable, m_state, is used to determine the actual type. In effect, it acts as the current index into either the subElement or dataElement list. As an object variable, it will be saved on the stack across subobject processing. Exceptions and Problems Fatal exceptions are processed via the standard MyMoneyException method. Due to differences in implementation between GnuCash and KMM, it is not always possible to provide an absolutely correct conversion. When such a problem situation is recognized, a message, along with any relevant variable data, is passed to the main class, and used to produce a report when processing terminates. Anonymizer When debugging problems, it is often useful to have a trace of what is happening within the module. However, in view of the sensitive nature of personal finance data, most users will be reluctant to provide this. Accordingly, an anonymize (hide()) function is provided to handle data strings. These may either be passed through asis (non-personal data), blanked out (non-critical but possibly personal data), replaced with a generated version (required, but possibly personal), or randomized (monetary amounts). The action for each data item is determined in the object's constructor function along with the creation of the data element list. This module will later be used as the basis of a file anonymizer, which will enable users to safely provide us with a copy of their GnuCash files, and will allow us to test the structure, if not the data content, of the file. */ #ifndef MYMONEYGNCREADER_H #define MYMONEYGNCREADER_H // system includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #ifndef _GNCFILEANON #include "storage/imymoneystorageformat.h" #endif // _GNCFILEANON // not sure what these are for, but leave them in #define VERSION_0_60_XML 0x10000010 // Version 0.5 file version info #define VERSION_0_61_XML 0x10000011 // use 8 bytes for MyMoneyMoney objects #define GNUCASH_ID_KEY "GNUCASH_ID" class MyMoneyAccount; class MyMoneySecurity; class MyMoneyTransaction; class MyMoneySplit; typedef QMap map_accountIds; typedef map_accountIds::iterator map_accountIds_iter; typedef map_accountIds::const_iterator map_accountIds_citer; typedef QMap map_elementVersions; class MyMoneyGncReader; class QIODevice; class QDate; class QTextCodec; -class IMyMoneySerialize; -class IMyMoneyStorage; +class MyMoneyStorageMgr; class QXmlAttributes; class QXmlInputSource; class QXmlSimpleReader; /** GncObject is the base class for the various objects in the gnucash file Beyond the first level XML objects, elements will be of one of three types: 1. Sub object elements, which require creation of another object to process 2. Data object elements, which are only followed by data to be stored in a variable (m_v array) 3. Ignored objects, data not needed and not included herein */ class GncKvp; class GncObject { public: GncObject(); virtual ~GncObject() {} // make sure to have impl of all virtual rtns to avoid vtable errors? protected: friend class XmlReader; friend class MyMoneyGncReader; // check for sub object element; if it is, create the object GncObject *isSubElement(const QString &elName, const QXmlAttributes& elAttrs); // check for data element; if so, set data pointer bool isDataElement(const QString &elName, const QXmlAttributes& elAttrs); // process start element for 'this'; normally for attribute checking; other initialization done in constructor virtual void initiate(const QString&, const QXmlAttributes&) { return ; }; // a sub object has completed; process the data it gathered virtual void endSubEl(GncObject *) { m_dataPtr = 0; return ; }; // store data for data element void storeData(const QString& pData) { // NB - data MAY come in chunks, and may need to be anonymized if (m_dataPtr != 0) m_dataPtr->append(hide(pData, m_anonClass)); } // following is provided only for a future file anonymizer QString getData() const { return ((m_dataPtr != 0) ? *m_dataPtr : ""); }; void resetDataPtr() { m_dataPtr = 0; }; // process end element for 'this'; usually to convert to KMM format virtual void terminate() { return ; }; void setVersion(const QString& v) { m_version = v; return; }; QString version() const { return (m_version); }; // some gnucash elements have version attribute; check it void checkVersion(const QString&, const QXmlAttributes&, const map_elementVersions&); // get name of element processed by 'this' QString getElName() const { return (m_elementName); }; // pass 'main' pointer to object void setPm(MyMoneyGncReader *pM) { pMain = pM; }; const QString getKvpValue(const QString& key, const QString& type = QString()) const; // debug only void debugDump(); // called by isSubElement to create appropriate sub object virtual GncObject *startSubEl() { return (0); }; // called by isDataElement to set variable pointer virtual void dataEl(const QXmlAttributes&) { m_dataPtr = &(m_v[m_state]); m_anonClass = m_anonClassList[m_state]; }; // return gnucash data string variable pointer virtual QString var(int i) const; // anonymize data virtual QString hide(QString, unsigned int); unsigned int kvpCount() const { return (m_kvpList.count()); }; //! MyMoneyGncReader *pMain; // pointer to 'main' class // used at start of each transaction so same money hide factor is applied to all splits void adjustHideFactor(); QString m_elementName; // save 'this' element's name QString m_version; // and it's gnucash version const QString *m_subElementList; // list of sub object element names for 'this' unsigned int m_subElementListCount; // count of above const QString *m_dataElementList; // ditto for data elements unsigned int m_dataElementListCount; QString *m_dataPtr; // pointer to m_v variable for current data item mutable QList m_v; // storage for variable pointers unsigned int m_state; // effectively, the index to subElementList or dataElementList, whichever is currently in use const unsigned int *m_anonClassList; enum anonActions {ASIS, SUPPRESS, NXTACC, NXTEQU, NXTPAY, NXTSCHD, MAYBEQ, MONEY1, MONEY2}; // anonymize actions - see hide() unsigned int m_anonClass; // class of current data item for anonymizer static double m_moneyHideFactor; // a per-transaction factor QList m_kvpList; //! }; // ***************************************************************************** // This is the 'grandfather' object representing the gnucash file as a whole class GncFile : public GncObject { public: GncFile(); ~GncFile(); private: enum iSubEls {BOOK, COUNT, CMDTY, PRICE, ACCT, TX, TEMPLATES, SCHEDULES, END_FILE_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); bool m_processingTemplates; // gnc uses same transaction element for ordinary and template tx's; this will distinguish bool m_bookFound; // to detect multi-book files }; // The following are 'utility' objects, which occur within several other object types // ************* GncKvp******************************************** // Key/value pairs, which are introduced by the 'slot' element // Consist of slot:key (the 'name' of the kvp), and slot:value (the data value) // the slot value also contains a slot type (string, integer, etc) implemented as an XML attribute // kvp's may be nested class GncKvp : public GncObject { public: GncKvp(); ~GncKvp(); //protected: friend class MyMoneyGncReader; QString key() const { return (var(KEY)); }; QString value() const { return (var(VALUE)); }; QString type() const { return (m_kvpType); }; const GncKvp getKvp(unsigned int i) const { return (m_kvpList[i]); }; private: // subsidiary objects/elements enum KvpSubEls {KVP, END_Kvp_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); // data elements enum KvpDataEls {KEY, VALUE, END_Kvp_DELS }; virtual void dataEl(const QXmlAttributes&); QString m_kvpType; // type is an XML attribute }; // ************* GncLot******************************************** // KMM doesn't have support for lots as yet class GncLot : public GncObject { public: GncLot(); ~GncLot(); protected: friend class MyMoneyGncReader; private: }; // **************************************************************************** // commodity specification. consists of // cmdty:space - either ISO4217 if this cmdty is a currency, or, usually, the name of a stock exchange // cmdty:id - ISO4217 currency symbol, or 'ticker symbol' class GncCmdtySpec : public GncObject { public: GncCmdtySpec(); ~GncCmdtySpec(); protected: friend class MyMoneyGncReader; friend class GncTransaction; bool isCurrency() const { return (m_v[CMDTYSPC] == QString("ISO4217")); }; QString id() const { return (m_v[CMDTYID]); }; QString space() const { return (m_v[CMDTYSPC]); }; private: // data elements enum CmdtySpecDataEls {CMDTYSPC, CMDTYID, END_CmdtySpec_DELS}; virtual QString hide(QString, unsigned int); }; // ********************************************************************* // date; maybe one of two types, ts:date which is date/time, gdate which is date only // we do not preserve time data (at present) class GncDate : public GncObject { public: GncDate(); ~GncDate(); protected: friend class MyMoneyGncReader; friend class GncPrice; friend class GncTransaction; friend class GncSplit; friend class GncSchedule; friend class GncRecurrence; const QDate date() const { return (QDate::fromString(m_v[TSDATE].section(' ', 0, 0), Qt::ISODate)); }; private: // data elements enum DateDataEls {TSDATE, GDATE, END_Date_DELS}; virtual void dataEl(const QXmlAttributes&) { m_dataPtr = &(m_v[TSDATE]); m_anonClass = GncObject::ASIS; } ; // treat both date types the same }; /** Following are the main objects within the gnucash file, which correspond largely one-for-one with similar objects in the kmymoney structure, apart from schedules which gnc splits between template (transaction data) and schedule (date data) */ //******************************************************************** class GncCountData : public GncObject { public: GncCountData(); ~GncCountData(); private: virtual void initiate(const QString&, const QXmlAttributes&); virtual void terminate(); QString m_countType; // type of element being counted }; //******************************************************************** class GncCommodity : public GncObject { public: GncCommodity(); ~GncCommodity(); protected: friend class MyMoneyGncReader; // access data values bool isCurrency() const { return (var(SPACE) == QString("ISO4217")); }; QString space() const { return (var(SPACE)); }; QString id() const { return (var(ID)); }; QString name() const { return (var(NAME)); }; QString fraction() const { return (var(FRACTION)); }; private: virtual void terminate(); // data elements enum {SPACE, ID, NAME, FRACTION, END_Commodity_DELS}; }; // ************* GncPrice******************************************** class GncPrice : public GncObject { public: GncPrice(); ~GncPrice(); protected: friend class MyMoneyGncReader; // access data values const GncCmdtySpec *commodity() const { return (m_vpCommodity); }; const GncCmdtySpec *currency() const { return (m_vpCurrency); }; QString value() const { return (var(VALUE)); }; QDate priceDate() const { return (m_vpPriceDate->date()); }; private: virtual void terminate(); // sub object elements enum PriceSubEls {CMDTY, CURR, PRICEDATE, END_Price_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); // data elements enum PriceDataEls {VALUE, END_Price_DELS }; GncCmdtySpec *m_vpCommodity, *m_vpCurrency; GncDate *m_vpPriceDate; }; // ************* GncAccount******************************************** class GncAccount : public GncObject { public: GncAccount(); ~GncAccount(); protected: friend class MyMoneyGncReader; // access data values GncCmdtySpec *commodity() const { return (m_vpCommodity); }; QString id() const { return (var(ID)); }; QString name() const { return (var(NAME)); }; QString desc() const { return (var(DESC)); }; QString type() const { return (var(TYPE)); }; QString parent() const { return (var(PARENT)); }; private: // subsidiary objects/elements enum AccountSubEls {CMDTY, KVP, LOTS, END_Account_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); virtual void terminate(); // data elements enum AccountDataEls {ID, NAME, DESC, TYPE, PARENT, END_Account_DELS }; GncCmdtySpec *m_vpCommodity; }; // ************* GncSplit******************************************** class GncSplit : public GncObject { public: GncSplit(); ~GncSplit(); protected: friend class MyMoneyGncReader; // access data values QString id() const { return (var(ID)); }; QString memo() const { return (var(MEMO)); }; QString recon() const { return (var(RECON)); }; QString value() const { return (var(VALUE)); }; QString qty() const { return (var(QTY)); }; QString acct() const { return (var(ACCT)); }; const QDate reconDate() const { QDate x = QDate(); return (m_vpDateReconciled == NULL ? x : m_vpDateReconciled->date()); }; private: // subsidiary objects/elements enum TransactionSubEls {RECDATE, END_Split_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); // data elements enum SplitDataEls {ID, MEMO, RECON, VALUE, QTY, ACCT, END_Split_DELS }; GncDate *m_vpDateReconciled; }; // ************* GncTransaction******************************************** class GncTransaction : public GncObject { public: GncTransaction(bool processingTemplates); ~GncTransaction(); protected: friend class MyMoneyGncReader; // access data values QString id() const { return (var(ID)); }; QString no() const { return (var(NO)); }; QString desc() const { return (var(DESC)); }; QString currency() const { return (m_vpCurrency == NULL ? QString() : m_vpCurrency->id()); }; QDate dateEntered() const { return (m_vpDateEntered->date()); }; QDate datePosted() const { return (m_vpDatePosted->date()); }; bool isTemplate() const { return (m_template); }; unsigned int splitCount() const { return (m_splitList.count()); }; const GncObject *getSplit(unsigned int i) const { return (m_splitList.at(i)); }; private: // subsidiary objects/elements enum TransactionSubEls {CURRCY, POSTED, ENTERED, SPLIT, KVP, END_Transaction_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); virtual void terminate(); const GncKvp getKvp(unsigned int i) const { return (m_kvpList.at(i)); }; // data elements enum TransactionDataEls {ID, NO, DESC, END_Transaction_DELS }; GncCmdtySpec *m_vpCurrency; GncDate *m_vpDateEntered, *m_vpDatePosted; mutable QList m_splitList; bool m_template; // true if this is a template for scheduled transaction }; // ************* GncTemplateSplit******************************************** class GncTemplateSplit : public GncObject { public: GncTemplateSplit(); ~GncTemplateSplit(); protected: friend class MyMoneyGncReader; // access data values QString id() const { return (var(ID)); }; QString memo() const { return (var(MEMO)); }; QString recon() const { return (var(RECON)); }; QString value() const { return (var(VALUE)); }; QString qty() const { return (var(QTY)); }; QString acct() const { return (var(ACCT)); }; private: const GncKvp getKvp(unsigned int i) const { return (m_kvpList[i]); }; // subsidiary objects/elements enum TemplateSplitSubEls {KVP, END_TemplateSplit_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); // data elements enum TemplateSplitDataEls {ID, MEMO, RECON, VALUE, QTY, ACCT, END_TemplateSplit_DELS }; }; // ************* GncSchedule******************************************** class GncFreqSpec; class GncRecurrence; class GncSchedDef; class GncSchedule : public GncObject { public: GncSchedule(); ~GncSchedule(); protected: friend class MyMoneyGncReader; // access data values QString name() const { return (var(NAME)); }; QString enabled() const { return var(ENABLED); }; QString autoCreate() const { return (var(AUTOC)); }; QString autoCrNotify() const { return (var(AUTOCN)); }; QString autoCrDays() const { return (var(AUTOCD)); }; QString advCrDays() const { return (var(ADVCD)); }; QString advCrRemindDays() const { return (var(ADVRD)); }; QString instanceCount() const { return (var(INSTC)); }; QString numOccurs() const { return (var(NUMOCC)); }; QString remOccurs() const { return (var(REMOCC)); }; QString templId() const { return (var(TEMPLID)); }; QDate startDate() const { QDate x = QDate(); return (m_vpStartDate == NULL ? x : m_vpStartDate->date()); }; QDate lastDate() const { QDate x = QDate(); return (m_vpLastDate == NULL ? x : m_vpLastDate->date()); }; QDate endDate() const { QDate x = QDate(); return (m_vpEndDate == NULL ? x : m_vpEndDate->date()); }; const GncFreqSpec *getFreqSpec() const { return (m_vpFreqSpec); }; const GncSchedDef *getSchedDef() const { return (m_vpSchedDef); }; private: // subsidiary objects/elements enum ScheduleSubEls {STARTDATE, LASTDATE, ENDDATE, FREQ, RECURRENCE, DEFINST, END_Schedule_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); virtual void terminate(); // data elements enum ScheduleDataEls {NAME, ENABLED, AUTOC, AUTOCN, AUTOCD, ADVCD, ADVRD, INSTC, NUMOCC, REMOCC, TEMPLID, END_Schedule_DELS }; GncDate *m_vpStartDate, *m_vpLastDate, *m_vpEndDate; GncFreqSpec *m_vpFreqSpec; mutable QList m_vpRecurrence; // gnc handles multiple occurrences GncSchedDef *m_vpSchedDef; }; // ************* GncFreqSpec******************************************** class GncFreqSpec : public GncObject { public: GncFreqSpec(); ~GncFreqSpec(); protected: friend class MyMoneyGncReader; // access data values (only interval type used at present) QString intervalType() const { return (var(INTVT)); }; private: // subsidiary objects/elements enum FreqSpecSubEls {COMPO, END_FreqSpec_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); // data elements enum FreqSpecDataEls {INTVT, MONTHLY, DAILY, WEEKLY, INTVI, INTVO, INTVD, END_FreqSpec_DELS}; virtual void terminate(); mutable QList m_fsList; }; // ************* GncRecurrence******************************************** // this object replaces GncFreqSpec from Gnucash 2.2 onwards class GncRecurrence : public GncObject { public: GncRecurrence(); ~GncRecurrence(); protected: friend class MyMoneyGncReader; // access data values QDate startDate() const { QDate x = QDate(); return (m_vpStartDate == NULL ? x : m_vpStartDate->date()); }; QString mult() const { return (var(MULT)); }; QString periodType() const { return (var(PERIODTYPE)); }; QString getFrequency() const; private: // subsidiary objects/elements enum RecurrenceSubEls {STARTDATE, END_Recurrence_SELS }; virtual GncObject *startSubEl(); virtual void endSubEl(GncObject *); // data elements enum RecurrenceDataEls {MULT, PERIODTYPE, END_Recurrence_DELS}; virtual void terminate(); GncDate *m_vpStartDate; }; // ************* GncSchedDef******************************************** // This is a sub-object of GncSchedule, (sx:deferredInstance) function currently unknown class GncSchedDef : public GncObject { public: GncSchedDef(); ~GncSchedDef(); protected: friend class MyMoneyGncReader; private: // subsidiary objects/elements }; // **************************************************************************************** /** XML Reader The XML reader is an implementation of the Qt SAX2 XML parser. It determines the type of object represented by the XMl, and calls the appropriate object functions */ // ***************************************************************************************** class XmlReader : public QXmlDefaultHandler { protected: friend class MyMoneyGncReader; XmlReader(MyMoneyGncReader *pM); // keep pointer to 'main' void processFile(QIODevice*); // main entry point of reader // define xml content handler functions bool startDocument(); bool startElement(const QString&, const QString&, const QString&, const QXmlAttributes&); bool endElement(const QString&, const QString&, const QString&); bool characters(const QString &); bool endDocument(); private: QXmlInputSource *m_source; QXmlSimpleReader *m_reader; QStack m_os; // stack of sub objects GncObject *m_co; // current object, for ease of coding (=== m_os.top) MyMoneyGncReader *pMain; // the 'main' pointer, to pass on to objects bool m_headerFound; // check for gnc-v2 header #ifdef _GNCFILEANON int lastType; // 0 = start element, 1 = data, 2 = end element int indentCount; #endif // _GNCFILEANON }; /** MyMoneyGncReader - Main class for this module Controls overall operation of the importer */ #ifndef _GNCFILEANON -class MyMoneyGncReader : public IMyMoneyStorageFormat +class MyMoneyGncReader : public IMyMoneyOperationsFormat { #else class MyMoneyGncReader { #endif // _GNCFILEANON public: MyMoneyGncReader(); virtual ~MyMoneyGncReader(); /** * Import a GnuCash XML file * * @param pDevice : pointer to GnuCash file * @param storage : pointer to MyMoneySerialize storage * * @return void * */ #ifndef _GNCFILEANON - void readFile(QIODevice* pDevice, IMyMoneySerialize* storage); // main entry point, IODevice is gnucash file - void writeFile(QIODevice*, IMyMoneySerialize*) { + void readFile(QIODevice* pDevice, MyMoneyStorageMgr *storage); // main entry point, IODevice is gnucash file + void writeFile(QIODevice*, MyMoneyStorageMgr*) { return ; }; // dummy entry needed by kmymoneywiew. we will not be writing #else void readFile(QString, QString); #endif // _GNCFILEANON QTextCodec *m_decoder; protected: friend class GncObject; // pity we can't just say GncObject. And compiler doesn't like multiple friends on one line... friend class GncFile; // there must be a better way... friend class GncDate; friend class GncCmdtySpec; friend class GncKvp; friend class GncLot; friend class GncCountData; friend class GncCommodity; friend class GncPrice; friend class GncAccount; friend class GncTransaction; friend class GncSplit; friend class GncTemplateTransaction; friend class GncTemplateSplit; friend class GncSchedule; friend class GncFreqSpec; friend class GncRecurrence; friend class XmlReader; #ifndef _GNCFILEANON /** functions to convert gnc objects to our equivalent */ void convertCommodity(const GncCommodity *); void convertPrice(const GncPrice *); void convertAccount(const GncAccount *); void convertTransaction(const GncTransaction *); void convertSplit(const GncSplit *); void saveTemplateTransaction(GncTransaction *t) { m_templateList.append(t); }; void convertSchedule(const GncSchedule *); void convertFreqSpec(const GncFreqSpec *); void convertRecurrence(const GncRecurrence *); #else /** functions to convert gnc objects to our equivalent */ void convertCommodity(const GncCommodity *) { return; }; void convertPrice(const GncPrice *) { return; }; void convertAccount(const GncAccount *) { return; }; void convertTransaction(const GncTransaction *) { return; }; void convertSplit(const GncSplit *) { return; }; void saveTemplateTransaction(GncTransaction *t) { return; }; void convertSchedule(const GncSchedule *) { return; }; void convertFreqSpec(const GncFreqSpec *) { return; }; #endif // _GNCFILEANON /** to post messages for final report */ void postMessage(const QString&, const unsigned int, const char *); void postMessage(const QString&, const unsigned int, const char *, const char *); void postMessage(const QString&, const unsigned int, const char *, const char *, const char *); void postMessage(const QString&, const unsigned int, const QStringList&); void setProgressCallback(void(*callback)(int, int, const QString&)); void signalProgress(int current, int total, const QString& = ""); /** user options */ /** Scheduled Transactions Due to differences in implementation, it is not always possible to import scheduled transactions correctly. Though best efforts are made, it may be that some imported transactions cause problems within kmymoney. An attempt is made within the importer to identify potential problem transactions, and setting this option will cause them to be dropped from the file. A report of which were dropped, and why, will be produced. m_dropSuspectSchedules - drop suspect scheduled transactions */ bool m_dropSuspectSchedules; /** Investments In kmymoney, all accounts representing investments (stocks, shares, bonds, etc.) must have an associated investment account (e.g. a broker account). The stock account holds the share balance, the investment account a money balance. Gnucash does not do this, so we cannot automate this function. If you have investments, you must select one of the following options. 0 - create a separate investment account for each stock with the same name as the stock 1 - create a single investment account to hold all stocks - you will be asked for a name 2 - create multiple investment accounts - you will be asked for a name for each stock N.B. :- option 2 doesn't really work quite as desired at present */ unsigned int m_investmentOption; /** Online quotes The user has the option to use the Finance::Quote system, as used by GnuCash, to retrieve online share price quotes */ bool m_useFinanceQuote; /** Tx Notes handling Under some usage conditions, non-split GnuCash transactions may contain residual, usually incorrect, memo data which is not normally visible to the user. When imported into KMyMoney however, due to display differences, this data can become visible. Often, these transactions will have a Notes field describing the real purpose of the transaction. If this option is selected, these notes, if present, will be used to override the extraneous memo data." */ bool m_useTxNotes; // set gnucash counts (not always accurate!) void setGncCommodityCount(int i) { m_gncCommodityCount = i; }; void setGncAccountCount(int i) { m_gncAccountCount = i; }; void setGncTransactionCount(int i) { m_gncTransactionCount = i; }; void setGncScheduleCount(int i) { m_gncScheduleCount = i; }; void setSmallBusinessFound(bool b) { m_smallBusinessFound = b; }; void setBudgetsFound(bool b) { m_budgetsFound = b; }; void setLotsFound(bool b) { m_lotsFound = b; }; /* Debug Options If you don't know what these are, best leave them alone. gncdebug - produce general debug messages xmldebug - produce a trace of the gnucash file XML bAnonymize - hide personal data (account names, payees, etc., randomize money amounts) */ bool gncdebug; // general debug messages bool xmldebug; // xml trace bool bAnonymize; // anonymize input static double m_fileHideFactor; // an overall anonymization factor to be applied to all items bool developerDebug; private: void setOptions(); // to set user options from dialog void setFileHideFactor(); // the following handles the gnucash indicator for a bad value (-1/0) which causes us probs QString convBadValue(QString gncValue) const { return (gncValue == "-1/0" ? "0/1" : gncValue); }; #ifndef _GNCFILEANON MyMoneyTransaction convertTemplateTransaction(const QString&, const GncTransaction *); void convertTemplateSplit(const QString&, const GncTemplateSplit *); #endif // _GNCFILEANON // wind up when all done void terminate(); QString buildReportSection(const QString&); bool writeReportToFile(const QList&); // main storage #ifndef _GNCFILEANON - IMyMoneyStorage *m_storage; + MyMoneyStorageMgr *m_storage; #else QTextStream oStream; #endif // _GNCFILEANON XmlReader *m_xr; /** to hold the callback pointer for the progress bar */ void (*m_progressCallback)(int, int, const QString&); // a map of which versions of the various elements (objects) we can import map_elementVersions m_versionList; // counters holding count data from the Gnc 'count-data' section int m_gncCommodityCount; int m_gncAccountCount; int m_gncTransactionCount; int m_gncScheduleCount; // flags indicating detection of features not (yet?) supported bool m_smallBusinessFound; bool m_budgetsFound; bool m_lotsFound; /** counters for reporting */ int m_commodityCount; int m_priceCount; int m_accountCount; int m_transactionCount; int m_templateCount; int m_scheduleCount; #ifndef _GNCFILEANON // counters for error reporting int m_ccCount, m_orCount, m_scCount; // currency counter QMap m_currencyCount; /** * Map gnucash vs. Kmm ids for accounts, equities, schedules, price sources */ QMap m_mapIds; QString m_rootId; // save the root id for terminate() QMap m_mapEquities; QMap m_mapSchedules; QMap m_mapSources; /** * A list of stock accounts (gnc ids) which will be held till the end so we can implement the user's investment option */ QList m_stockList; /** * Temporary storage areas for transaction processing */ QString m_txCommodity; // save commodity for current transaction QString m_txPayeeId; // gnc has payee at tx level, we need it at split level QDate m_txDatePosted; // ditto for post date QString m_txChequeNo; // ditto for cheque number /** In kmm, the order of splits is critical to some operations. These * areas will hold the splits until we've read them all */ QList m_splitList, m_liabilitySplitList, m_otherSplitList; bool m_potentialTransfer; // to determine whether this might be a transfer /** Schedules are processed through 3 different functions, any of which may set this flag */ bool m_suspectSchedule; /** * A holding area for template txs while we're waiting for the schedules */ QList m_templateList; /** Hold a list of suspect schedule ids for later processing? */ QList m_suspectList; /** * To hold message data till final report */ QMap m_messageList; /** * Internal utility functions */ QString createPayee(const QString&); // create a payee and return it's id QString createOrphanAccount(const QString&); // create unknown account and return the id QDate incrDate(QDate lastDate, unsigned char interval, unsigned int intervalCount); // for date calculations MyMoneyAccount checkConsistency(MyMoneyAccount& parent, MyMoneyAccount& child); // gnucash is sometimes TOO flexible void checkInvestmentOption(QString stockId); // implement user investment option void getPriceSource(MyMoneySecurity stock, QString gncSource); #endif // _GNCFILEANON }; #endif // MYMONEYGNCREADER_H diff --git a/kmymoney/plugins/interfaces/kmmviewinterface.cpp b/kmymoney/plugins/interfaces/kmmviewinterface.cpp index a5045ab2f..9155cb202 100644 --- a/kmymoney/plugins/interfaces/kmmviewinterface.cpp +++ b/kmymoney/plugins/interfaces/kmmviewinterface.cpp @@ -1,74 +1,74 @@ /*************************************************************************** viewinterface.cpp ------------------- begin : Wed Jan 5 2005 copyright : (C) 2005 Thomas Baumgart email : ipwizard@users.sourceforge.net (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmmviewinterface.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyview.h" #include "selectedtransactions.h" KMyMoneyPlugin::KMMViewInterface::KMMViewInterface(KMyMoneyView* view, QObject* parent, const char* name) : ViewInterface(parent, name), m_view(view) { connect(m_view, &KMyMoneyView::accountSelected, this, &ViewInterface::accountSelected); connect(m_view, &KMyMoneyView::transactionsSelected, this, &ViewInterface::transactionsSelected); connect(m_view, &KMyMoneyView::accountReconciled, this, &ViewInterface::accountReconciled); // connect(app, &KMyMoneyApp::institutionSelected, this, &ViewInterface::institutionSelected); connect(m_view, &KMyMoneyView::viewStateChanged, this, &ViewInterface::viewStateChanged); connect(m_view, &KMyMoneyView::kmmFilePlugin, this, &ViewInterface::kmmFilePlugin); } -bool KMyMoneyPlugin::KMMViewInterface::readFile(const QUrl &url, IMyMoneyStorageFormat *pExtReader) +bool KMyMoneyPlugin::KMMViewInterface::readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader) { return m_view->readFile(url, pExtReader); } void KMyMoneyPlugin::KMMViewInterface::slotRefreshViews() { m_view->slotRefreshViews(); } bool KMyMoneyPlugin::KMMViewInterface::fileOpen() { return m_view->fileOpen(); } //KMyMoneyViewBase* KMyMoneyPlugin::KMMViewInterface::addPage(const QString& item, const QString& icon) //{ // return m_view->addBasePage(item, icon); //} //void KMyMoneyPlugin::KMMViewInterface::addWidget(KMyMoneyViewBase* view, QWidget* w) //{ // if (view && w) // view->addWidget(w); //} diff --git a/kmymoney/plugins/interfaces/kmmviewinterface.h b/kmymoney/plugins/interfaces/kmmviewinterface.h index 3b86ded7a..ef2ad9b7a 100644 --- a/kmymoney/plugins/interfaces/kmmviewinterface.h +++ b/kmymoney/plugins/interfaces/kmmviewinterface.h @@ -1,104 +1,104 @@ /*************************************************************************** kmmviewinterface.h ------------------- begin : Wed Jan 5 2005 copyright : (C) 2005 Thomas Baumgart email : ipwizard@users.sourceforge.net (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMMVIEWINTERFACE_H #define KMMVIEWINTERFACE_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes class KMyMoneyView; // ---------------------------------------------------------------------------- // Project Includes #include "viewinterface.h" namespace KMyMoneyPlugin { /** * This class represents the implementation of the * ViewInterface. */ class KMMViewInterface : public ViewInterface { Q_OBJECT public: KMMViewInterface(KMyMoneyView* view, QObject* parent, const char* name = 0); ~KMMViewInterface() {} /** * This method returns a pointer to a newly created view * with title @p item and icon @p pixmap. * * @param item Name of view * @param icon name for the icon to be used for the view * * @return pointer to KMyMoneyViewBase object */ // KMyMoneyViewBase* addPage(const QString& item, const QString& icon); /** * This method allows to add a widget to the view * created with addPage() * * @param view pointer to view object * @param w pointer to widget */ // void addWidget(KMyMoneyViewBase* view, QWidget* w); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ - bool readFile(const QUrl &url, IMyMoneyStorageFormat *pExtReader = nullptr) override; + bool readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader = nullptr) override; /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ bool fileOpen() override; /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ void slotRefreshViews() override; private: KMyMoneyView* m_view; }; } // namespace #endif diff --git a/kmymoney/plugins/viewinterface.h b/kmymoney/plugins/viewinterface.h index 2cd772726..f148e4ad1 100644 --- a/kmymoney/plugins/viewinterface.h +++ b/kmymoney/plugins/viewinterface.h @@ -1,155 +1,155 @@ /*************************************************************************** viewinterface.h ------------------- begin : Wed Jan 5 2005 copyright : (C) 2005 Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ // krazy:excludeall=dpointer #ifndef VIEWINTERFACE_H #define VIEWINTERFACE_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneymoney.h" #include "mymoneytransaction.h" #include "mymoneysplit.h" #include namespace KMyMoneyRegister { class SelectedTransactions; } class MyMoneyInstitution; class MyMoneyAccount; class MyMoneySplit; class MyMoneyTransaction; -class IMyMoneyStorageFormat; +class IMyMoneyOperationsFormat; namespace KMyMoneyPlugin { /** * This abstract class represents the ViewInterface to * add new view pages to the JanusWidget of KMyMoney. It * also gives access to the account context menu. */ class KMM_PLUGIN_EXPORT ViewInterface : public QObject { Q_OBJECT public: explicit ViewInterface(QObject* parent, const char* name = 0); virtual ~ViewInterface(); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ - virtual bool readFile(const QUrl &url, IMyMoneyStorageFormat *pExtReader = nullptr) = 0; + virtual bool readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader = nullptr) = 0; /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ virtual bool fileOpen() = 0; /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ virtual void slotRefreshViews() = 0; /** * This method creates a new page in the application. * See KPageWidget::addPage() for details. */ // virtual KMyMoneyViewBase* addPage(const QString& item, const QString& icon) = 0; /** * This method adds a widget to the layout of the view * created with addPage() * * @param view pointer to view widget * @param w widget to be added to @p page */ // virtual void addWidget(KMyMoneyViewBase* view, QWidget* w) = 0; Q_SIGNALS: /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& acc); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is emitted when a new institution has been selected by * the GUI. If no institution is selected or the selection is removed, * @a institution is identical to MyMoneyInstitution(). This signal is used * by plugins to get information about changes. */ // void institutionSelected(const MyMoneyInstitution& institution); /** * This signal is emitted when an account has been successfully reconciled * and all transactions are updated in the engine. It can be used by plugins * to create reconciliation reports. * * @param account the account data * @param date the reconciliation date as provided through the dialog * @param startingBalance the starting balance as provided through the dialog * @param endingBalance the ending balance as provided through the dialog * @param transactionList reference to QList of QPair containing all * transaction/split pairs processed by the reconciliation. */ void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); void viewStateChanged(bool); void kmmFilePlugin(unsigned int); }; } // namespace #endif diff --git a/kmymoney/reports/tests/kreportsview-test.h b/kmymoney/reports/tests/kreportsview-test.h index 255d4b5fa..926c08475 100644 --- a/kmymoney/reports/tests/kreportsview-test.h +++ b/kmymoney/reports/tests/kreportsview-test.h @@ -1,63 +1,63 @@ /*************************************************************************** mymoneyaccounttest.h ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KREPORTSVIEWTEST_H #define KREPORTSVIEWTEST_H #include #include "mymoneyfile.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" class KReportsViewTest : public QObject { Q_OBJECT private: MyMoneyAccount *m; - MyMoneySeqAccessMgr* storage; + MyMoneyStorageMgr* storage; MyMoneyFile* file; private Q_SLOTS: void init(); void cleanup(); void testNetWorthSingle(); void testNetWorthOfsetting(); void testNetWorthOpeningPrior(); void testNetWorthDateFilter(); void testSpendingEmpty(); void testSingleTransaction(); void testSubAccount(); void testFilterIEvsIE(); void testFilterALvsAL(); void testFilterALvsIE(); void testFilterAllvsIE(); void testFilterBasics(); void testMultipleCurrencies(); void testAdvancedFilter(); void testColumnType(); void testXMLWrite(); void testQueryBasics(); void testCashFlowAnalysis(); void testAccountQuery(); void testInvestment(); void testWebQuotes(); void testDateFormat(); void testHasReferenceTo(); }; #endif diff --git a/kmymoney/reports/tests/pivotgrid-test.cpp b/kmymoney/reports/tests/pivotgrid-test.cpp index ca3a1f001..88b0fbb74 100644 --- a/kmymoney/reports/tests/pivotgrid-test.cpp +++ b/kmymoney/reports/tests/pivotgrid-test.cpp @@ -1,175 +1,175 @@ /*************************************************************************** pivotgridtest.cpp ------------------- copyright : (C) 2002-2005 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "pivotgrid-test.h" #include #include "reportstestcommon.h" #include "pivotgrid.h" #include "mymoneyinstitution.h" #include "mymoneysecurity.h" #include "mymoneypayee.h" #include "mymoneyenums.h" using namespace reports; using namespace test; QTEST_GUILESS_MAIN(PivotGridTest) void PivotGridTest::init() { - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Thomas Baumgart"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount(QString("Checking Account"), eMyMoney::Account::Type::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset); acCredit = makeAccount(QString("Credit Card"), eMyMoney::Account::Type::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability); acSolo = makeAccount(QString("Solo"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acParent = makeAccount(QString("Parent"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acChild = makeAccount(QString("Child"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acForeign = makeAccount(QString("Foreign"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acSecondChild = makeAccount(QString("Second Child"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acGrandChild1 = makeAccount(QString("Grand Child 1"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild); acGrandChild2 = makeAccount(QString("Grand Child 2"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void PivotGridTest::cleanup() { file->detachStorage(storage); delete storage; } void PivotGridTest::testCellAddValue() { PivotCell a; QVERIFY(a == MyMoneyMoney()); QVERIFY(a.m_stockSplit == MyMoneyMoney::ONE); QVERIFY(a.m_postSplit == MyMoneyMoney()); QVERIFY(a.formatMoney("", 2) == MyMoneyMoney().formatMoney("", 2)); PivotCell b(MyMoneyMoney(13, 10)); QVERIFY(b == MyMoneyMoney(13, 10)); QVERIFY(b.m_stockSplit == MyMoneyMoney::ONE); QVERIFY(b.m_postSplit == MyMoneyMoney()); QVERIFY(b.formatMoney("", 2) == MyMoneyMoney(13, 10).formatMoney("", 2)); PivotCell s(b); QVERIFY(s == MyMoneyMoney(13, 10)); QVERIFY(s.m_stockSplit == MyMoneyMoney::ONE); QVERIFY(s.m_postSplit == MyMoneyMoney()); QVERIFY(s.formatMoney("", 2) == MyMoneyMoney(13, 10).formatMoney("", 2)); s = PivotCell::stockSplit(MyMoneyMoney(1, 2)); QVERIFY(s == MyMoneyMoney()); QVERIFY(s.m_stockSplit == MyMoneyMoney(1, 2)); QVERIFY(s.m_postSplit == MyMoneyMoney()); QVERIFY(s.formatMoney("", 2) == MyMoneyMoney().formatMoney("", 2)); a += MyMoneyMoney::ONE; a += MyMoneyMoney(2, 1); QVERIFY(a == MyMoneyMoney(3, 1)); QVERIFY(a.m_stockSplit == MyMoneyMoney::ONE); QVERIFY(a.m_postSplit == MyMoneyMoney()); QVERIFY(a.formatMoney("", 2) == MyMoneyMoney(3, 1).formatMoney("", 2)); a += s; QVERIFY(a == MyMoneyMoney(3, 1)); QVERIFY(a.m_stockSplit == MyMoneyMoney(1, 2)); QVERIFY(a.m_postSplit == MyMoneyMoney()); QVERIFY(a.formatMoney("", 2) == MyMoneyMoney(15, 10).formatMoney("", 2)); a += MyMoneyMoney(3, 1); a += MyMoneyMoney(3, 1); QVERIFY(a == MyMoneyMoney(3, 1)); QVERIFY(a.m_stockSplit == MyMoneyMoney(1, 2)); QVERIFY(a.m_postSplit == MyMoneyMoney(6, 1)); QVERIFY(a.formatMoney("", 2) == MyMoneyMoney(75, 10).formatMoney("", 2)); } void PivotGridTest::testCellAddCell() { PivotCell a, b; a += MyMoneyMoney(3, 1); a += PivotCell::stockSplit(MyMoneyMoney(2, 1)); a += MyMoneyMoney(4, 1); QVERIFY(a == MyMoneyMoney(3, 1)); QVERIFY(a.m_stockSplit == MyMoneyMoney(2, 1)); QVERIFY(a.m_postSplit == MyMoneyMoney(4, 1)); QVERIFY(a.formatMoney("", 2) == MyMoneyMoney(10, 1).formatMoney("", 2)); b += MyMoneyMoney(4, 1); b += PivotCell::stockSplit(MyMoneyMoney(4, 1)); b += MyMoneyMoney(16, 1); QVERIFY(b == MyMoneyMoney(4, 1)); QVERIFY(b.m_stockSplit == MyMoneyMoney(4, 1)); QVERIFY(b.m_postSplit == MyMoneyMoney(16, 1)); QVERIFY(b.formatMoney("", 2) == MyMoneyMoney(32, 1).formatMoney("", 2)); a += b; QVERIFY(a == MyMoneyMoney(3, 1)); QVERIFY(a.m_stockSplit == MyMoneyMoney(8, 1)); QVERIFY(a.m_postSplit == MyMoneyMoney(48, 1)); QVERIFY(a.formatMoney("", 2) == MyMoneyMoney(72, 1).formatMoney("", 2)); } void PivotGridTest::testCellRunningSum() { PivotCell a; MyMoneyMoney runningSum(12, 10); a += MyMoneyMoney(3, 1); a += PivotCell::stockSplit(MyMoneyMoney(125, 100)); a += MyMoneyMoney(134, 10); QVERIFY(a.m_stockSplit != MyMoneyMoney::ONE); QVERIFY(a.m_postSplit != MyMoneyMoney()); runningSum = a.calculateRunningSum(runningSum); QVERIFY(runningSum == MyMoneyMoney(1865, 100)); QVERIFY(a.formatMoney("", 2) == MyMoneyMoney(1865, 100).formatMoney("", 2)); QVERIFY(a.m_stockSplit == MyMoneyMoney::ONE); QVERIFY(a.m_postSplit == MyMoneyMoney()); } diff --git a/kmymoney/reports/tests/pivotgrid-test.h b/kmymoney/reports/tests/pivotgrid-test.h index 8778e00aa..74682c25c 100644 --- a/kmymoney/reports/tests/pivotgrid-test.h +++ b/kmymoney/reports/tests/pivotgrid-test.h @@ -1,53 +1,53 @@ /*************************************************************************** pivotgridtest.h ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef PIVOTGRIDTEST_H #define PIVOTGRIDTEST_H #include namespace reports { class PivotGridTest; } #define KMM_MYMONEY_UNIT_TESTABLE friend class reports::PivotGridTest; #include "mymoneyfile.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" namespace reports { class PivotGridTest : public QObject { Q_OBJECT private: - MyMoneySeqAccessMgr* storage; + MyMoneyStorageMgr* storage; MyMoneyFile* file; private Q_SLOTS: void init(); void cleanup(); void testCellAddValue(); void testCellAddCell(); void testCellRunningSum(); }; } #endif // PIVOTGRIDTEST_H diff --git a/kmymoney/reports/tests/pivottable-test.cpp b/kmymoney/reports/tests/pivottable-test.cpp index 924f9cc65..7acbe760c 100644 --- a/kmymoney/reports/tests/pivottable-test.cpp +++ b/kmymoney/reports/tests/pivottable-test.cpp @@ -1,1077 +1,1077 @@ /*************************************************************************** pivottabletest.cpp ------------------- copyright : (C) 2002-2005 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "pivottable-test.h" #include #include #include #include // DOH, mmreport.h uses this without including it!! #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneyreport.h" #include "mymoneystatement.h" #include "mymoneysplit.h" #include "mymoneypayee.h" #include "mymoneyexception.h" #include "mymoneystoragedump.h" #include "mymoneystoragexml.h" #include "mymoneyenums.h" #include "pivottable.h" #include "reportstestcommon.h" #include "kmymoneysettings.h" using namespace reports; using namespace test; QTEST_GUILESS_MAIN(PivotTableTest) void PivotTableTest::setup() { } void PivotTableTest::init() { - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Thomas Baumgart"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount(QString("Checking Account"), eMyMoney::Account::Type::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset); acCredit = makeAccount(QString("Credit Card"), eMyMoney::Account::Type::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability); acSolo = makeAccount(QString("Solo"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acParent = makeAccount(QString("Parent"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acChild = makeAccount(QString("Child"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acForeign = makeAccount(QString("Foreign"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acSecondChild = makeAccount(QString("Second Child"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acGrandChild1 = makeAccount(QString("Grand Child 1"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild); acGrandChild2 = makeAccount(QString("Grand Child 2"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acChild); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void PivotTableTest::cleanup() { file->detachStorage(storage); delete storage; } void PivotTableTest::testNetWorthSingle() { try { MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2004, 7, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); writeTabletoCSV(networth_f); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"][ReportAccount(acChecking)][eActual][5] == moCheckingOpen); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"][ReportAccount(acChecking)][eActual][6] == moCheckingOpen); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"].m_total[eActual][5] == moCheckingOpen); QVERIFY(networth_f.m_grid.m_total[eActual][0] == moZero); QVERIFY(networth_f.m_grid.m_total[eActual][4] == moZero); QVERIFY(networth_f.m_grid.m_total[eActual][5] == moCheckingOpen); QVERIFY(networth_f.m_grid.m_total[eActual][6] == moCheckingOpen); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void PivotTableTest::testNetWorthOfsetting() { // Test the net worth report to make sure it picks up the opening balance for two // accounts opened during the period of the report, one asset & one liability. Test // that it calculates the totals correctly. MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); QVERIFY(networth_f.m_grid["Liability"]["Credit Card"][ReportAccount(acCredit)][eActual][7] == -moCreditOpen); QVERIFY(networth_f.m_grid.m_total[eActual][0] == moZero); QVERIFY(networth_f.m_grid.m_total[eActual][12] == moCheckingOpen + moCreditOpen); } void PivotTableTest::testNetWorthOpeningPrior() { // Test the net worth report to make sure it's picking up opening balances PRIOR to // the period of the report. MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2005, 8, 1), QDate(2005, 12, 31)); filter.setName("Net Worth Opening Prior 1"); XMLandback(filter); PivotTable networth_f(filter); writeTabletoCSV(networth_f); QVERIFY(networth_f.m_grid["Liability"]["Credit Card"].m_total[eActual][0] == -moCreditOpen); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"].m_total[eActual][0] == moCheckingOpen); QVERIFY(networth_f.m_grid.m_total[eActual][0] == moCheckingOpen + moCreditOpen); QVERIFY(networth_f.m_grid.m_total[eActual][1] == moCheckingOpen + moCreditOpen); // Test the net worth report to make sure that transactions prior to the report // period are included in the opening balance TransactionHelper t1(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acChecking, acChild); filter.setName("Net Worth Opening Prior 2"); PivotTable networth_f2(filter); writeTabletoCSV(networth_f2); MyMoneyMoney m1 = (networth_f2.m_grid["Liability"]["Credit Card"].m_total[eActual][1]); MyMoneyMoney m2 = (-moCreditOpen + moParent); QVERIFY((networth_f2.m_grid["Liability"]["Credit Card"].m_total[eActual][1]) == (-moCreditOpen + moParent)); QVERIFY(networth_f2.m_grid["Asset"]["Checking Account"].m_total[eActual][1] == moCheckingOpen - moChild); QVERIFY(networth_f2.m_grid.m_total[eActual][1] == moCheckingOpen + moCreditOpen - moChild - moParent); } void PivotTableTest::testNetWorthDateFilter() { // Test a net worth report whose period is prior to the time any accounts are open, // so the report should be zero. MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2004, 2, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); QVERIFY(networth_f.m_grid.m_total[eActual][1] == moZero); } void PivotTableTest::testNetWorthOpening() { MyMoneyMoney openingBalance(12000000); auto acBasicAccount = makeAccount(QString("Basic Account"), eMyMoney::Account::Type::Checkings, openingBalance, QDate(2016, 1, 1), acAsset); auto ctBasicIncome = makeAccount(QString("Basic Income"), eMyMoney::Account::Type::Income, MyMoneyMoney(), QDate(2016, 1, 1), acIncome); auto ctBasicExpense = makeAccount(QString("Basic Expense"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2016, 1, 1), acExpense); TransactionHelper t1(QDate(2016, 7, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-6200000), acBasicAccount, ctBasicIncome); TransactionHelper t2(QDate(2016, 8, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-200000), acBasicAccount, ctBasicIncome); TransactionHelper t3(QDate(2016, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-200000), acBasicAccount, ctBasicIncome); TransactionHelper t4(QDate(2016, 10, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t5(QDate(2016, 11, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t6(QDate(2016, 12, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(100000), acBasicAccount, ctBasicExpense); TransactionHelper t7(QDate(2017, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t8(QDate(2017, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t9(QDate(2017, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t10(QDate(2017, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t11(QDate(2017, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(4500000), acBasicAccount, ctBasicExpense); TransactionHelper t12(QDate(2017, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); TransactionHelper t13(QDate(2017, 7, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(-100000), acBasicAccount, ctBasicIncome); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2016, 1, 1), QDate(2017, 12, 31)); filter.addAccount(acBasicAccount); XMLandback(filter); PivotTable nt_opening1(filter); writeTabletoCSV(nt_opening1, "networth-opening-1.csv"); QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][0] == MyMoneyMoney()); // opening value on 1st Jan 2016 is 12000000, but before that i.e. 31st Dec 2015 opening value is 0 for (auto i = 1; i <= 6; ++i) QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][i] == openingBalance); QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][7] == openingBalance + MyMoneyMoney(6200000)); QVERIFY(nt_opening1.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][12] == MyMoneyMoney(18700000)); // value after t6 transaction filter.setDateFilter(QDate(2017, 1, 1), QDate(2017, 12, 31)); XMLandback(filter); PivotTable nt_opening2(filter); writeTabletoCSV(nt_opening2, "networth-opening-2.csv"); QVERIFY(nt_opening2.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][0] == MyMoneyMoney(18700000)); // opening value is equall to the value after t6 transaction QVERIFY(nt_opening2.m_grid["Asset"]["Basic Account"][ReportAccount(acBasicAccount)][eActual][12] == MyMoneyMoney(14800000)); } void PivotTableTest::testSpendingEmpty() { // test a spending report with no entries MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); XMLandback(filter); PivotTable spending_f1(filter); QVERIFY(spending_f1.m_grid.m_total[eActual].m_total == moZero); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == moZero); } void PivotTableTest::testSingleTransaction() { // Test a single transaction TransactionHelper t(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.setName("Spending with Single Transaction.html"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoHTML(spending_f, "Spending with Single Transaction.html"); QVERIFY(spending_f.m_grid["Expense"]["Solo"][ReportAccount(acSolo)][eActual][1] == moSolo); QVERIFY(spending_f.m_grid["Expense"]["Solo"].m_total[eActual][1] == moSolo); QVERIFY(spending_f.m_grid["Expense"]["Solo"].m_total[eActual][0] == moZero); QVERIFY(spending_f.m_grid.m_total[eActual][1] == (-moSolo)); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == (-moSolo)); filter.clearTransactionFilter(); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); QVERIFY(networth_f.m_grid["Asset"]["Checking Account"].m_total[eActual][2] == (moCheckingOpen - moSolo)); } void PivotTableTest::testSubAccount() { // Test a sub-account with a value, under an account with a value TransactionHelper t1(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.setDetailLevel(MyMoneyReport::eDetailAll); filter.setName("Spending with Sub-Account"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoHTML(spending_f, "Spending with Sub-Account.html"); QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acParent)][eActual][2] == moParent); QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acChild)][eActual][2] == moChild); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual][2] == moParent + moChild); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual][1] == moZero); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual].m_total == moParent + moChild); QVERIFY(spending_f.m_grid.m_total[eActual][2] == (-moParent - moChild)); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == (-moParent - moChild)); filter.clearTransactionFilter(); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.setName("Net Worth with Sub-Account"); XMLandback(filter); PivotTable networth_f(filter); writeTabletoHTML(networth_f, "Net Worth with Sub-Account.html"); QVERIFY(networth_f.m_grid["Liability"]["Credit Card"].m_total[eActual][3] == moParent + moChild - moCreditOpen); QVERIFY(networth_f.m_grid.m_total[eActual][4] == -moParent - moChild + moCreditOpen + moCheckingOpen); } void PivotTableTest::testFilterIEvsIE() { // Test that removing an income/spending account will remove the entry from an income/spending report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addCategory(acChild); filter.addCategory(acSolo); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid["Expense"]["Parent"].m_total[eActual][2] == moChild); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][1] == moSolo); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo - moChild); } void PivotTableTest::testFilterALvsAL() { // Test that removing an asset/liability account will remove the entry from an asset/liability report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addAccount(acChecking); filter.addCategory(acChild); filter.addCategory(acSolo); XMLandback(filter); PivotTable networth_f(filter); QVERIFY(networth_f.m_grid.m_total[eActual][2] == -moSolo + moCheckingOpen); } void PivotTableTest::testFilterALvsIE() { // Test that removing an asset/liability account will remove the entry from an income/spending report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addAccount(acChecking); QVERIFY(file->transactionList(filter).count() == 1); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][2] == moZero); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][1] == moSolo); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo); } void PivotTableTest::testFilterAllvsIE() { // Test that removing an asset/liability account AND an income/expense // category will remove the entry from an income/spending report TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addAccount(acCredit); filter.addCategory(acChild); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][1] == moZero); QVERIFY(spending_f.m_grid["Expense"].m_total[eActual][2] == moChild); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moChild); } void PivotTableTest::testFilterBasics() { // Test that the filters are operating the way that the reports expect them to TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); MyMoneyTransactionFilter filter; filter.clear(); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addCategory(acSolo); filter.setReportAllSplits(false); filter.setConsiderCategory(true); QVERIFY(file->transactionList(filter).count() == 1); filter.addCategory(acParent); QVERIFY(file->transactionList(filter).count() == 3); filter.addAccount(acChecking); QVERIFY(file->transactionList(filter).count() == 1); filter.clear(); filter.setDateFilter(QDate(2004, 9, 1), QDate(2005, 1, 1).addDays(-1)); filter.addCategory(acParent); filter.addAccount(acCredit); filter.setReportAllSplits(false); filter.setConsiderCategory(true); QVERIFY(file->transactionList(filter).count() == 2); } void PivotTableTest::testMultipleCurrencies() { MyMoneyMoney moCanOpening(0.0, 10); MyMoneyMoney moJpyOpening(0.0, 10); MyMoneyMoney moCanPrice(0.75, 100); MyMoneyMoney moJpyPrice(0.010, 1000); MyMoneyMoney moJpyPrice2(0.011, 1000); MyMoneyMoney moJpyPrice3(0.014, 1000); MyMoneyMoney moJpyPrice4(0.0395, 10000); MyMoneyMoney moCanTransaction(100.0, 10); MyMoneyMoney moJpyTransaction(100.0, 10); QString acCanChecking = makeAccount(QString("Canadian Checking"), eMyMoney::Account::Type::Checkings, moCanOpening, QDate(2003, 11, 15), acAsset, "CAD"); QString acJpyChecking = makeAccount(QString("Japanese Checking"), eMyMoney::Account::Type::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY"); QString acCanCash = makeAccount(QString("Canadian"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acForeign, "CAD"); QString acJpyCash = makeAccount(QString("Japanese"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acForeign, "JPY"); makePrice("CAD", QDate(2004, 1, 1), MyMoneyMoney(moCanPrice)); makePrice("JPY", QDate(2004, 1, 1), MyMoneyMoney(moJpyPrice)); makePrice("JPY", QDate(2004, 5, 1), MyMoneyMoney(moJpyPrice2)); makePrice("JPY", QDate(2004, 6, 30), MyMoneyMoney(moJpyPrice3)); makePrice("JPY", QDate(2004, 7, 15), MyMoneyMoney(moJpyPrice4)); TransactionHelper t1(QDate(2004, 2, 20), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY"); TransactionHelper t2(QDate(2004, 3, 20), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY"); TransactionHelper t3(QDate(2004, 4, 20), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(moJpyTransaction), acJpyChecking, acJpyCash, "JPY"); TransactionHelper t4(QDate(2004, 2, 20), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD"); TransactionHelper t5(QDate(2004, 3, 20), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD"); TransactionHelper t6(QDate(2004, 4, 20), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(moCanTransaction), acCanChecking, acCanCash, "CAD"); #if 0 QFile g("multicurrencykmy.xml"); g.open(QIODevice::WriteOnly); MyMoneyStorageXML xml; - IMyMoneyStorageFormat& interface = xml; - interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); + IMyMoneyOperationsFormat& interface = xml; + interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); #endif MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setDetailLevel(MyMoneyReport::eDetailAll); filter.setConvertCurrency(true); filter.setName("Multiple Currency Spending Rerport (with currency conversion)"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoCSV(spending_f); // test single foreign currency QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][1] == (moCanTransaction*moCanPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][2] == (moCanTransaction*moCanPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][3] == (moCanTransaction*moCanPrice)); // test multiple foreign currencies under a common parent QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][1] == (moJpyTransaction*moJpyPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][2] == (moJpyTransaction*moJpyPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][3] == (moJpyTransaction*moJpyPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"].m_total[eActual][1] == (moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice)); QVERIFY(spending_f.m_grid["Expense"]["Foreign"].m_total[eActual].m_total == (moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice + moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice + moJpyTransaction*moJpyPrice + moCanTransaction*moCanPrice)); // Test the report type where we DO NOT convert the currency filter.setConvertCurrency(false); filter.setDetailLevel(MyMoneyReport::eDetailAll); filter.setName("Multiple Currency Spending Report (WITHOUT currency conversion)"); XMLandback(filter); PivotTable spending_fnc(filter); writeTabletoCSV(spending_fnc); QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][1] == (moCanTransaction)); QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][2] == (moCanTransaction)); QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acCanCash)][eActual][3] == (moCanTransaction)); QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][1] == (moJpyTransaction)); QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][2] == (moJpyTransaction)); QVERIFY(spending_fnc.m_grid["Expense"]["Foreign"][ReportAccount(acJpyCash)][eActual][3] == (moJpyTransaction)); filter.setConvertCurrency(true); filter.clearTransactionFilter(); filter.setName("Multiple currency net worth"); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); writeTabletoCSV(networth_f); // test single foreign currency QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][1] == (moCanOpening*moCanPrice)); QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][2] == ((moCanOpening - moCanTransaction)*moCanPrice)); QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][3] == ((moCanOpening - moCanTransaction - moCanTransaction)*moCanPrice)); QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][4] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice)); QVERIFY(networth_f.m_grid["Asset"]["Canadian Checking"][ReportAccount(acCanChecking)][eActual][12] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice)); // test Stable currency price, fluctuating account balance QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][1] == (moJpyOpening*moJpyPrice)); QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][2] == ((moJpyOpening - moJpyTransaction)*moJpyPrice)); QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][3] == ((moJpyOpening - moJpyTransaction - moJpyTransaction)*moJpyPrice)); QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][4] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice)); // test Fluctuating currency price, stable account balance QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][5] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice2)); QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][6] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice3)); QVERIFY(networth_f.m_grid["Asset"]["Japanese Checking"][ReportAccount(acJpyChecking)][eActual][7] == ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice4)); // test multiple currencies totalled up QVERIFY(networth_f.m_grid["Asset"].m_total[eActual][4] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice) + ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice)); QVERIFY(networth_f.m_grid["Asset"].m_total[eActual][5] == ((moCanOpening - moCanTransaction - moCanTransaction - moCanTransaction)*moCanPrice) + ((moJpyOpening - moJpyTransaction - moJpyTransaction - moJpyTransaction)*moJpyPrice2) + moCheckingOpen); } void PivotTableTest::testAdvancedFilter() { // test more advanced filtering capabilities // amount { TransactionHelper t1(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setAmountFilter(moChild, moChild); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moChild); } // payee (specific) { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moThomas, acCredit, acParent, QString(), "Thomas Baumgart"); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.addPayee(MyMoneyFile::instance()->payeeByName("Thomas Baumgart").id()); filter.setName("Spending with Payee Filter"); XMLandback(filter); PivotTable spending_f(filter); writeTabletoHTML(spending_f, "Spending with Payee Filter.html"); QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acParent)][eActual][10] == moThomas); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moThomas); } // payee (no payee) { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moNoPayee, acCredit, acParent, QString(), QString()); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.addPayee(QString()); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid["Expense"]["Parent"][ReportAccount(acParent)][eActual][10] == moNoPayee); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moNoPayee); } // text { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moThomas, acCredit, acParent, QString(), "Thomas Baumgart"); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setTextFilter(QRegExp("Thomas")); XMLandback(filter); PivotTable spending_f(filter); } // type (payment, deposit, transfer) { TransactionHelper t1(QDate(2004, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), -moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), moChild, acCredit, acChecking); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.addType((int)eMyMoney::TransactionFilter::Type::Payments); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo); filter.clearTransactionFilter(); filter.addType((int)eMyMoney::TransactionFilter::Type::Deposits); XMLandback(filter); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == moParent1); filter.clearTransactionFilter(); filter.addType((int)eMyMoney::TransactionFilter::Type::Transfers); XMLandback(filter); PivotTable spending_f3(filter); QVERIFY(spending_f3.m_grid.m_total[eActual].m_total == moZero); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2004, 12, 31)); XMLandback(filter); PivotTable networth_f4(filter); QVERIFY(networth_f4.m_grid["Asset"].m_total[eActual][11] == moCheckingOpen + moChild); QVERIFY(networth_f4.m_grid["Liability"].m_total[eActual][11] == - moCreditOpen + moChild); QVERIFY(networth_f4.m_grid.m_total[eActual][9] == moCheckingOpen + moCreditOpen); QVERIFY(networth_f4.m_grid.m_total[eActual][10] == moCheckingOpen + moCreditOpen); } // state (reconciled, cleared, not) { TransactionHelper t1(QDate(2004, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); QList splits = t1.splits(); splits[0].setReconcileFlag(eMyMoney::Split::State::Cleared); splits[1].setReconcileFlag(eMyMoney::Split::State::Cleared); t1.modifySplit(splits[0]); t1.modifySplit(splits[1]); t1.update(); splits.clear(); splits = t2.splits(); splits[0].setReconcileFlag(eMyMoney::Split::State::Reconciled); splits[1].setReconcileFlag(eMyMoney::Split::State::Reconciled); t2.modifySplit(splits[0]); t2.modifySplit(splits[1]); t2.update(); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo); filter.addState((int)eMyMoney::TransactionFilter::State::Reconciled); XMLandback(filter); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == -moSolo - moParent1); filter.clearTransactionFilter(); filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); XMLandback(filter); PivotTable spending_f3(filter); QVERIFY(spending_f3.m_grid.m_total[eActual].m_total == -moChild - moParent2); } // number { TransactionHelper t1(QDate(2004, 10, 31), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); QList splits = t1.splits(); splits[0].setNumber("1"); splits[1].setNumber("1"); t1.modifySplit(splits[0]); t1.modifySplit(splits[1]); t1.update(); splits.clear(); splits = t2.splits(); splits[0].setNumber("2"); splits[1].setNumber("2"); t2.modifySplit(splits[0]); t2.modifySplit(splits[1]); t2.update(); splits.clear(); splits = t3.splits(); splits[0].setNumber("3"); splits[1].setNumber("3"); t3.modifySplit(splits[0]); t3.modifySplit(splits[1]); t3.update(); splits.clear(); splits = t2.splits(); splits[0].setNumber("4"); splits[1].setNumber("4"); t4.modifySplit(splits[0]); t4.modifySplit(splits[1]); t4.update(); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); filter.setNumberFilter("1", "3"); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo - moParent1 - moParent2); } // blank dates { TransactionHelper t1y1(QDate(2003, 10, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2y1(QDate(2003, 11, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3y1(QDate(2003, 12, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t1y2(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2004, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2004, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t1y3(QDate(2005, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2y3(QDate(2005, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3y3(QDate(2005, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(), QDate(2004, 7, 1)); XMLandback(filter); PivotTable spending_f(filter); QVERIFY(spending_f.m_grid.m_total[eActual].m_total == -moSolo - moParent1 - moParent2 - moSolo - moParent1 - moParent2); filter.clearTransactionFilter(); XMLandback(filter); PivotTable spending_f2(filter); QVERIFY(spending_f2.m_grid.m_total[eActual].m_total == -moSolo - moParent1 - moParent2 - moSolo - moParent1 - moParent2 - moSolo - moParent1 - moParent2); } } void PivotTableTest::testColumnType() { // test column type values of other than 'month' TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setDateFilter(QDate(2003, 12, 31), QDate(2005, 12, 31)); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(MyMoneyReport::eBiMonths); XMLandback(filter); PivotTable spending_b(filter); QVERIFY(spending_b.m_grid.m_total[eActual][0] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][1] == -moParent1 - moSolo); QVERIFY(spending_b.m_grid.m_total[eActual][2] == -moParent2 - moSolo); QVERIFY(spending_b.m_grid.m_total[eActual][3] == -moParent); QVERIFY(spending_b.m_grid.m_total[eActual][4] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][5] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][6] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][7] == -moSolo); QVERIFY(spending_b.m_grid.m_total[eActual][8] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][9] == -moParent1); QVERIFY(spending_b.m_grid.m_total[eActual][10] == moZero); QVERIFY(spending_b.m_grid.m_total[eActual][11] == -moParent2); QVERIFY(spending_b.m_grid.m_total[eActual][12] == moZero); filter.setColumnType(MyMoneyReport::eQuarters); XMLandback(filter); PivotTable spending_q(filter); QVERIFY(spending_q.m_grid.m_total[eActual][0] == moZero); QVERIFY(spending_q.m_grid.m_total[eActual][1] == -moSolo - moParent); QVERIFY(spending_q.m_grid.m_total[eActual][2] == -moSolo - moParent); QVERIFY(spending_q.m_grid.m_total[eActual][3] == moZero); QVERIFY(spending_q.m_grid.m_total[eActual][4] == moZero); QVERIFY(spending_q.m_grid.m_total[eActual][5] == -moSolo); QVERIFY(spending_q.m_grid.m_total[eActual][6] == -moParent1); QVERIFY(spending_q.m_grid.m_total[eActual][7] == -moParent2); QVERIFY(spending_q.m_grid.m_total[eActual][8] == moZero); filter.setRowType(MyMoneyReport::eAssetLiability); filter.setName("Net Worth by Quarter"); XMLandback(filter); PivotTable networth_q(filter); writeTabletoHTML(networth_q, "Net Worth by Quarter.html"); QVERIFY(networth_q.m_grid.m_total[eActual][1] == moZero); QVERIFY(networth_q.m_grid.m_total[eActual][2] == -moSolo - moParent); QVERIFY(networth_q.m_grid.m_total[eActual][3] == -moSolo - moParent - moSolo - moParent + moCheckingOpen); QVERIFY(networth_q.m_grid.m_total[eActual][4] == -moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][5] == -moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][6] == -moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][7] == -moParent1 - moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][8] == -moParent2 - moParent1 - moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_q.m_grid.m_total[eActual][9] == -moParent2 - moParent1 - moSolo - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(MyMoneyReport::eYears); XMLandback(filter); PivotTable spending_y(filter); QVERIFY(spending_y.m_grid.m_total[eActual][0] == moZero); QVERIFY(spending_y.m_grid.m_total[eActual][1] == -moSolo - moParent - moSolo - moParent); QVERIFY(spending_y.m_grid.m_total[eActual][2] == -moSolo - moParent); QVERIFY(spending_y.m_grid.m_total[eActual].m_total == -moSolo - moParent - moSolo - moParent - moSolo - moParent); filter.setRowType(MyMoneyReport::eAssetLiability); XMLandback(filter); PivotTable networth_y(filter); QVERIFY(networth_y.m_grid.m_total[eActual][1] == moZero); QVERIFY(networth_y.m_grid.m_total[eActual][2] == -moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); QVERIFY(networth_y.m_grid.m_total[eActual][3] == -moSolo - moParent - moSolo - moParent - moSolo - moParent + moCheckingOpen + moCreditOpen); // Test days-based reports TransactionHelper t1d1(QDate(2004, 7, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2d1(QDate(2004, 7, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3d1(QDate(2004, 7, 4), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t1d2(QDate(2004, 7, 14), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2d2(QDate(2004, 7, 15), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3d2(QDate(2004, 7, 20), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t1d3(QDate(2004, 8, 2), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2d3(QDate(2004, 8, 3), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3d3(QDate(2004, 8, 4), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); filter.setDateFilter(QDate(2004, 7, 2), QDate(2004, 7, 14)); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(MyMoneyReport::eMonths); filter.setColumnsAreDays(true); filter.setName("Spending by Days"); XMLandback(filter); PivotTable spending_days(filter); writeTabletoHTML(spending_days, "Spending by Days.html"); QVERIFY(spending_days.m_grid.m_total[eActual][2] == -moParent2); QVERIFY(spending_days.m_grid.m_total[eActual][12] == -moSolo); QVERIFY(spending_days.m_grid.m_total[eActual].m_total == -moSolo - moParent2); // set the first day of the week to 1 QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedKingdom)); filter.setDateFilter(QDate(2004, 7, 2), QDate(2004, 8, 1)); filter.setRowType(MyMoneyReport::eExpenseIncome); filter.setColumnType(static_cast(7)); filter.setColumnsAreDays(true); filter.setName("Spending by Weeks"); XMLandback(filter); PivotTable spending_weeks(filter); writeTabletoHTML(spending_weeks, "Spending by Weeks.html"); // restore the locale QLocale::setDefault(QLocale::system()); QVERIFY(spending_weeks.m_grid.m_total[eActual][0] == -moParent2); QVERIFY(spending_weeks.m_grid.m_total[eActual][1] == moZero); QVERIFY(spending_weeks.m_grid.m_total[eActual][2] == -moSolo - moParent1); QVERIFY(spending_weeks.m_grid.m_total[eActual][3] == -moParent2); QVERIFY(spending_weeks.m_grid.m_total[eActual][4] == moZero); QVERIFY(spending_weeks.m_grid.m_total[eActual].m_total == -moSolo - moParent - moParent2); } void PivotTableTest::testInvestment() { try { // Equities eqStock1 = makeEquity("Stock1", "STK1"); eqStock2 = makeEquity("Stock2", "STK2"); // Accounts acInvestment = makeAccount("Investment", eMyMoney::Account::Type::Investment, moZero, QDate(2004, 1, 1), acAsset); acStock1 = makeAccount("Stock 1", eMyMoney::Account::Type::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1); acStock2 = makeAccount("Stock 2", eMyMoney::Account::Type::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2); acDividends = makeAccount("Dividends", eMyMoney::Account::Type::Income, moZero, QDate(2004, 1, 1), acIncome); // Transactions // Date Action Shares Price Stock Asset Income InvTransactionHelper s1b1(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1b2(QDate(2004, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(1000.00), MyMoneyMoney(110.00), acStock1, acChecking, QString()); InvTransactionHelper s1s1(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(-200.00), MyMoneyMoney(120.00), acStock1, acChecking, QString()); InvTransactionHelper s1s2(QDate(2004, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(-200.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1r1(QDate(2004, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend), MyMoneyMoney(50.00), MyMoneyMoney(100.00), acStock1, QString(), acDividends); InvTransactionHelper s1r2(QDate(2004, 7, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend), MyMoneyMoney(50.00), MyMoneyMoney(80.00), acStock1, QString(), acDividends); InvTransactionHelper s1c1(QDate(2004, 8, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend), MyMoneyMoney(10.00), MyMoneyMoney(100.00), acStock1, acChecking, acDividends); InvTransactionHelper s1c2(QDate(2004, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend), MyMoneyMoney(10.00), MyMoneyMoney(120.00), acStock1, acChecking, acDividends); makeEquityPrice(eqStock1, QDate(2004, 10, 1), MyMoneyMoney(100.00)); // // Net Worth Report (with investments) // MyMoneyReport networth_r; networth_r.setRowType(MyMoneyReport::eAssetLiability); networth_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 12, 31).addDays(-1)); XMLandback(networth_r); PivotTable networth(networth_r); networth.dump("networth_i.html"); QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][1] == moZero); // 1000 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][2] == MyMoneyMoney(100000.0)); // 2000 shares @ $110.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][3] == MyMoneyMoney(220000.0)); // 1800 shares @ $120.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][4] == MyMoneyMoney(216000.0)); // 1600 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][5] == MyMoneyMoney(160000.0)); // 1650 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][6] == MyMoneyMoney(165000.0)); // 1700 shares @ $ 80.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][7] == MyMoneyMoney(136000.0)); // 1700 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][8] == MyMoneyMoney(170000.0)); // 1700 shares @ $120.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][9] == MyMoneyMoney(204000.0)); // 1700 shares @ $100.00 QVERIFY(networth.m_grid["Asset"]["Investment"].m_total[eActual][10] == MyMoneyMoney(170000.0)); #if 0 // Dump file & reports QFile g("investmentkmy.xml"); g.open(QIODevice::WriteOnly); MyMoneyStorageXML xml; - IMyMoneyStorageFormat& interface = xml; - interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); + IMyMoneyOperationsFormat& interface = xml; + interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); invtran.dump("invtran.html", "%1"); invhold.dump("invhold.html", "%1"); #endif } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void PivotTableTest::testBudget() { // 1. Budget on A, transactions on A { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acSolo, false, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // 2. Budget on B, not applying to sub accounts, transactions on B and B:1 { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acParent, false, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // - Both B and B:1 totals should show up // - B actuals compare against B budget // - B:1 actuals compare against 0 // 3. Budget on C, applying to sub accounts, transactions on C and C:1 and C:1:a { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acParent, true, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop , "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // - Only C totals show up, not C:1 or C:1:a totals // - C + C:1 totals compare against C budget // 4. Budget on D, not applying to sub accounts, budget on D:1 not applying, budget on D:2 applying. Transactions on D, D:1, D:2, D:2:a, D:2:b { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acParent, false, MyMoneyMoney(100.0)); budget += BudgetEntryHelper(QDate(2006, 1, 1), acChild, false, MyMoneyMoney(100.0)); budget += BudgetEntryHelper(QDate(2006, 1, 1), acSecondChild, true, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } // - Totals for D, D:1, D:2 show up. D:2:a and D:2:b do not // - D actuals (only) compare against D budget // - Ditto for D:1 // - D:2 acutals and children compare against D:2 budget // 5. Budget on E, no transactions on E { BudgetHelper budget; budget += BudgetEntryHelper(QDate(2006, 1, 1), acSolo, false, MyMoneyMoney(100.0)); MyMoneyReport report(MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, eMyMoney::TransactionFilter::Date::YearToDate, MyMoneyReport::eDetailTop, "Yearly Budgeted vs. Actual", "Default Report"); PivotTable table(report); } } void PivotTableTest::testHtmlEncoding() { MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAssetLiability); filter.setDateFilter(QDate(2004, 1, 1), QDate(2005, 1, 1).addDays(-1)); XMLandback(filter); PivotTable networth_f(filter); QByteArray encoding = QTextCodec::codecForLocale()->name(); QString html = networth_f.renderReport(QLatin1String("html"), encoding, filter.name(), false); QRegExp rx(QString::fromLatin1("**")); rx.setPatternSyntax(QRegExp::Wildcard); rx.setCaseSensitivity(Qt::CaseInsensitive); QVERIFY(rx.exactMatch(html)); } diff --git a/kmymoney/reports/tests/pivottable-test.h b/kmymoney/reports/tests/pivottable-test.h index 057929be9..faf52c0e2 100644 --- a/kmymoney/reports/tests/pivottable-test.h +++ b/kmymoney/reports/tests/pivottable-test.h @@ -1,70 +1,70 @@ /*************************************************************************** pivottabletest.h ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef PIVOTTABLETEST_H #define PIVOTTABLETEST_H #include namespace reports { class PivotTableTest; } #define KMM_MYMONEY_UNIT_TESTABLE friend class reports::PivotTableTest; #include "mymoneyfile.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" #include "reporttable.h" namespace reports { class PivotTableTest : public QObject { Q_OBJECT private: - MyMoneySeqAccessMgr* storage; + MyMoneyStorageMgr* storage; MyMoneyFile* file; private Q_SLOTS: void setup(); void init(); void cleanup(); void testNetWorthSingle(); void testNetWorthOfsetting(); void testNetWorthOpeningPrior(); void testNetWorthDateFilter(); void testNetWorthOpening(); void testSpendingEmpty(); void testSingleTransaction(); void testSubAccount(); void testFilterIEvsIE(); void testFilterALvsAL(); void testFilterALvsIE(); void testFilterAllvsIE(); void testFilterBasics(); void testMultipleCurrencies(); void testAdvancedFilter(); void testColumnType(); void testInvestment(); void testBudget(); void testHtmlEncoding(); }; } #endif // PIVOTTABLETEST_H diff --git a/kmymoney/reports/tests/querytable-test.cpp b/kmymoney/reports/tests/querytable-test.cpp index 5415d8be9..d0b56de0f 100644 --- a/kmymoney/reports/tests/querytable-test.cpp +++ b/kmymoney/reports/tests/querytable-test.cpp @@ -1,904 +1,904 @@ /*************************************************************************** querytabletest.cpp ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "querytable-test.h" #include #include #include #include "reportstestcommon.h" #include "querytable.h" #include "mymoneyinstitution.h" #include "mymoneyaccount.h" #include "mymoneysecurity.h" #include "mymoneyprice.h" #include "mymoneystoragedump.h" #include "mymoneyreport.h" #include "mymoneysplit.h" #include "mymoneypayee.h" #include "mymoneystatement.h" #include "mymoneystoragexml.h" #include "mymoneyexception.h" #include "kmymoneysettings.h" using namespace reports; using namespace test; QTEST_GUILESS_MAIN(QueryTableTest) void QueryTableTest::setup() { } void QueryTableTest::init() { - storage = new MyMoneySeqAccessMgr; + storage = new MyMoneyStorageMgr; file = MyMoneyFile::instance(); file->attachStorage(storage); MyMoneyFileTransaction ft; file->addCurrency(MyMoneySecurity("CAD", "Canadian Dollar", "C$")); file->addCurrency(MyMoneySecurity("USD", "US Dollar", "$")); file->addCurrency(MyMoneySecurity("JPY", "Japanese Yen", QChar(0x00A5), 1)); file->addCurrency(MyMoneySecurity("GBP", "British Pound", "#")); file->setBaseCurrency(file->currency("USD")); MyMoneyPayee payeeTest("Test Payee"); file->addPayee(payeeTest); MyMoneyPayee payeeTest2("Thomas Baumgart"); file->addPayee(payeeTest2); acAsset = (MyMoneyFile::instance()->asset().id()); acLiability = (MyMoneyFile::instance()->liability().id()); acExpense = (MyMoneyFile::instance()->expense().id()); acIncome = (MyMoneyFile::instance()->income().id()); acChecking = makeAccount(QString("Checking Account"), eMyMoney::Account::Type::Checkings, moCheckingOpen, QDate(2004, 5, 15), acAsset); acCredit = makeAccount(QString("Credit Card"), eMyMoney::Account::Type::CreditCard, moCreditOpen, QDate(2004, 7, 15), acLiability); acSolo = makeAccount(QString("Solo"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acParent = makeAccount(QString("Parent"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acChild = makeAccount(QString("Child"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 2, 11), acParent); acForeign = makeAccount(QString("Foreign"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2004, 1, 11), acExpense); acTax = makeAccount(QString("Tax"), eMyMoney::Account::Type::Expense, MyMoneyMoney(), QDate(2005, 1, 11), acExpense, "", true); MyMoneyInstitution i("Bank of the World", "", "", "", "", "", ""); file->addInstitution(i); inBank = i.id(); ft.commit(); } void QueryTableTest::cleanup() { file->detachStorage(storage); delete storage; } void QueryTableTest::testQueryBasics() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eCategory); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; filter.setQueryColumns(static_cast(cols)); // filter.setName("Transactions by Category"); XMLandback(filter); QueryTable qtbl_1(filter); writeTabletoHTML(qtbl_1, "Transactions by Category.html"); QList rows = qtbl_1.rows(); QVERIFY(rows.count() == 19); QVERIFY(rows[0][ListTable::ctCategoryType] == "Expense"); QVERIFY(rows[0][ListTable::ctCategory] == "Parent"); QVERIFY(rows[0][ListTable::ctPostDate] == "2004-02-01"); QVERIFY(rows[14][ListTable::ctCategoryType] == "Expense"); QVERIFY(rows[14][ListTable::ctCategory] == "Solo"); QVERIFY(rows[14][ListTable::ctPostDate] == "2005-01-01"); QVERIFY(MyMoneyMoney(rows[6][ListTable::ctValue]) == -(moParent1 + moParent2) * 3); QVERIFY(MyMoneyMoney(rows[10][ListTable::ctValue]) == -(moChild) * 3); QVERIFY(MyMoneyMoney(rows[16][ListTable::ctValue]) == -(moSolo) * 3); QVERIFY(MyMoneyMoney(rows[17][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); QVERIFY(MyMoneyMoney(rows[18][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eTopCategory); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; filter.setQueryColumns(static_cast(cols)); // filter.setName("Transactions by Top Category"); XMLandback(filter); QueryTable qtbl_2(filter); writeTabletoHTML(qtbl_2, "Transactions by Top Category.html"); rows = qtbl_2.rows(); QVERIFY(rows.count() == 16); QVERIFY(rows[0][ListTable::ctCategoryType] == "Expense"); QVERIFY(rows[0][ListTable::ctTopCategory] == "Parent"); QVERIFY(rows[0][ListTable::ctPostDate] == "2004-02-01"); QVERIFY(rows[8][ListTable::ctCategoryType] == "Expense"); QVERIFY(rows[8][ListTable::ctTopCategory] == "Parent"); QVERIFY(rows[8][ListTable::ctPostDate] == "2005-09-01"); QVERIFY(rows[12][ListTable::ctCategoryType] == "Expense"); QVERIFY(rows[12][ListTable::ctTopCategory] == "Solo"); QVERIFY(rows[12][ListTable::ctPostDate] == "2005-01-01"); QVERIFY(MyMoneyMoney(rows[9][ListTable::ctValue]) == -(moParent1 + moParent2 + moChild) * 3); QVERIFY(MyMoneyMoney(rows[13][ListTable::ctValue]) == -(moSolo) * 3); QVERIFY(MyMoneyMoney(rows[14][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); QVERIFY(MyMoneyMoney(rows[15][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account.html"); rows = qtbl_3.rows(); #if 1 QVERIFY(rows.count() == 19); QVERIFY(rows[1][ListTable::ctAccount] == "Checking Account"); QVERIFY(rows[1][ListTable::ctCategory] == "Solo"); QVERIFY(rows[1][ListTable::ctPostDate] == "2004-01-01"); QVERIFY(rows[15][ListTable::ctAccount] == "Credit Card"); QVERIFY(rows[15][ListTable::ctCategory] == "Parent"); QVERIFY(rows[15][ListTable::ctPostDate] == "2005-09-01"); #else QVERIFY(rows.count() == 12); QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); QVERIFY(rows[11][ListTable::ctAccount] == "Credit Card"); QVERIFY(rows[11][ListTable::ctCategory] == "Parent"); QVERIFY(rows[11][ListTable::ctPostDate] == "2005-09-01"); #endif QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == -(moSolo) * 3 + moCheckingOpen); QVERIFY(MyMoneyMoney(rows[17][ListTable::ctValue]) == -(moParent1 + moParent2 + moChild) * 3 + moCreditOpen); QVERIFY(MyMoneyMoney(rows[18][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::ePayee); filter.setName("Transactions by Payee"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCmemo | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_4(filter); writeTabletoHTML(qtbl_4, "Transactions by Payee.html"); rows = qtbl_4.rows(); QVERIFY(rows.count() == 14); QVERIFY(rows[0][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); QVERIFY(rows[7][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[7][ListTable::ctCategory] == "Parent: Child"); QVERIFY(rows[7][ListTable::ctPostDate] == "2004-11-07"); QVERIFY(rows[11][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[11][ListTable::ctCategory] == "Parent"); QVERIFY(rows[11][ListTable::ctPostDate] == "2005-09-01"); QVERIFY(MyMoneyMoney(rows[12][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3); QVERIFY(MyMoneyMoney(rows[13][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eMonth); filter.setName("Transactions by Month"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_5(filter); writeTabletoHTML(qtbl_5, "Transactions by Month.html"); rows = qtbl_5.rows(); QVERIFY(rows.count() == 23); QVERIFY(rows[0][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); QVERIFY(rows[12][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[12][ListTable::ctCategory] == "Parent: Child"); QVERIFY(rows[12][ListTable::ctPostDate] == "2004-11-07"); QVERIFY(rows[20][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[20][ListTable::ctCategory] == "Parent"); QVERIFY(rows[20][ListTable::ctPostDate] == "2005-09-01"); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == -moSolo); QVERIFY(MyMoneyMoney(rows[15][ListTable::ctValue]) == -(moChild) * 3); QVERIFY(MyMoneyMoney(rows[9][ListTable::ctValue]) == -moParent1 + moCheckingOpen); QVERIFY(MyMoneyMoney(rows[22][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); filter.setRowType(MyMoneyReport::eWeek); filter.setName("Transactions by Week"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_6(filter); writeTabletoHTML(qtbl_6, "Transactions by Week.html"); rows = qtbl_6.rows(); QVERIFY(rows.count() == 23); QVERIFY(rows[0][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[0][ListTable::ctCategory] == "Solo"); QVERIFY(rows[0][ListTable::ctPostDate] == "2004-01-01"); QVERIFY(rows[20][ListTable::ctPayee] == "Test Payee"); QVERIFY(rows[20][ListTable::ctCategory] == "Parent"); QVERIFY(rows[20][ListTable::ctPostDate] == "2005-09-01"); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == -moSolo); QVERIFY(MyMoneyMoney(rows[15][ListTable::ctValue]) == -(moChild) * 3); QVERIFY(MyMoneyMoney(rows[21][ListTable::ctValue]) == -moParent2); QVERIFY(MyMoneyMoney(rows[22][ListTable::ctValue]) == -(moParent1 + moParent2 + moSolo + moChild) * 3 + moCheckingOpen + moCreditOpen); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } // Test querytable::TableRow::operator> and operator== QueryTable::TableRow low; low[ListTable::ctPrice] = 'A'; low[ListTable::ctLastPrice] = 'B'; low[ListTable::ctBuyPrice] = 'C'; QueryTable::TableRow high; high[ListTable::ctPrice] = 'A'; high[ListTable::ctLastPrice] = 'C'; high[ListTable::ctBuyPrice] = 'B'; QueryTable::TableRow::setSortCriteria({ListTable::ctPrice, ListTable::ctLastPrice, ListTable::ctBuyPrice}); QVERIFY(low < high); QVERIFY(low <= high); QVERIFY(high > low); QVERIFY(high <= high); QVERIFY(high == high); } void QueryTableTest::testCashFlowAnalysis() { // // Test IRR calculations // CashFlowList list; list += CashFlowListItem(QDate(2004, 5, 3), MyMoneyMoney(1000.0)); list += CashFlowListItem(QDate(2004, 5, 20), MyMoneyMoney(59.0)); list += CashFlowListItem(QDate(2004, 6, 3), MyMoneyMoney(14.0)); list += CashFlowListItem(QDate(2004, 6, 24), MyMoneyMoney(92.0)); list += CashFlowListItem(QDate(2004, 7, 6), MyMoneyMoney(63.0)); list += CashFlowListItem(QDate(2004, 7, 25), MyMoneyMoney(15.0)); list += CashFlowListItem(QDate(2004, 8, 5), MyMoneyMoney(92.0)); list += CashFlowListItem(QDate(2004, 9, 2), MyMoneyMoney(18.0)); list += CashFlowListItem(QDate(2004, 9, 21), MyMoneyMoney(5.0)); list += CashFlowListItem(QDate(2004, 10, 16), MyMoneyMoney(-2037.0)); MyMoneyMoney IRR(list.IRR(), 1000); QVERIFY(IRR == MyMoneyMoney(1676, 1000)); list.pop_back(); list += CashFlowListItem(QDate(2004, 10, 16), MyMoneyMoney(-1358.0)); IRR = MyMoneyMoney(list.IRR(), 1000); QVERIFY(IRR.isZero()); } void QueryTableTest::testAccountQuery() { try { QString htmlcontext = QString("\n\n%1\n\n"); // // No transactions, opening balances only // MyMoneyReport filter; filter.setRowType(MyMoneyReport::eInstitution); filter.setName("Accounts by Institution (No transactions)"); XMLandback(filter); QueryTable qtbl_1(filter); writeTabletoHTML(qtbl_1, "Accounts by Institution (No transactions).html"); QList rows = qtbl_1.rows(); QVERIFY(rows.count() == 6); QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == moCheckingOpen); QVERIFY(rows[0][ListTable::ctEquityType].isEmpty()); QVERIFY(rows[2][ListTable::ctAccount] == "Credit Card"); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == moCreditOpen); QVERIFY(rows[2][ListTable::ctEquityType].isEmpty()); QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == moCheckingOpen + moCreditOpen); QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == moCheckingOpen + moCreditOpen); // // Adding in transactions // TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); filter.setRowType(MyMoneyReport::eInstitution); filter.setName("Accounts by Institution (With Transactions)"); XMLandback(filter); QueryTable qtbl_2(filter); rows = qtbl_2.rows(); QVERIFY(rows.count() == 6); QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == (moCheckingOpen - moSolo*3)); QVERIFY(rows[2][ListTable::ctAccount] == "Credit Card"); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); // // Account TYPES // filter.setRowType(MyMoneyReport::eAccountType); filter.setName("Accounts by Type"); XMLandback(filter); QueryTable qtbl_3(filter); rows = qtbl_3.rows(); QVERIFY(rows.count() == 5); QVERIFY(rows[0][ListTable::ctAccount] == "Checking Account"); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == (moCheckingOpen - moSolo * 3)); QVERIFY(rows[2][ListTable::ctAccount] == "Credit Card"); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == (moCreditOpen - (moParent1 + moParent2 + moChild) * 3)); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == moCheckingOpen - moSolo * 3); QVERIFY(MyMoneyMoney(rows[3][ListTable::ctValue]) == moCreditOpen - (moParent1 + moParent2 + moChild) * 3); QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == moCheckingOpen + moCreditOpen - (moParent1 + moParent2 + moSolo + moChild) * 3); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testInvestment() { try { // Equities eqStock1 = makeEquity("Stock1", "STK1"); eqStock2 = makeEquity("Stock2", "STK2"); eqStock3 = makeEquity("Stock3", "STK3"); eqStock4 = makeEquity("Stock4", "STK4"); // Accounts acInvestment = makeAccount("Investment", eMyMoney::Account::Type::Investment, moZero, QDate(2003, 11, 1), acAsset); acStock1 = makeAccount("Stock 1", eMyMoney::Account::Type::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock1); acStock2 = makeAccount("Stock 2", eMyMoney::Account::Type::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock2); acStock3 = makeAccount("Stock 3", eMyMoney::Account::Type::Stock, moZero, QDate(2003, 11, 1), acInvestment, eqStock3); acStock4 = makeAccount("Stock 4", eMyMoney::Account::Type::Stock, moZero, QDate(2004, 1, 1), acInvestment, eqStock4); acDividends = makeAccount("Dividends", eMyMoney::Account::Type::Income, moZero, QDate(2004, 1, 1), acIncome); acInterest = makeAccount("Interest", eMyMoney::Account::Type::Income, moZero, QDate(2004, 1, 1), acIncome); acFees = makeAccount("Fees", eMyMoney::Account::Type::Expense, moZero, QDate(2003, 11, 1), acExpense); // Transactions // Date Action Shares Price Stock Asset Income InvTransactionHelper s1b1(QDate(2003, 12, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock3, acChecking, QString()); InvTransactionHelper s1b2(QDate(2004, 1, 30), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(500.00), MyMoneyMoney(100.00), acStock4, acChecking, acFees, MyMoneyMoney(100.00)); InvTransactionHelper s1b3(QDate(2004, 1, 30), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(500.00), MyMoneyMoney(90.00), acStock4, acChecking, acFees, MyMoneyMoney(100.00)); InvTransactionHelper s1b4(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(1000.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1b5(QDate(2004, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(1000.00), MyMoneyMoney(110.00), acStock1, acChecking, QString()); InvTransactionHelper s1s1(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(-200.00), MyMoneyMoney(120.00), acStock1, acChecking, QString()); InvTransactionHelper s1s2(QDate(2004, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(-200.00), MyMoneyMoney(100.00), acStock1, acChecking, QString()); InvTransactionHelper s1s3(QDate(2004, 5, 30), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), MyMoneyMoney(-1000.00), MyMoneyMoney(120.00), acStock4, acChecking, acFees, MyMoneyMoney(200.00)); InvTransactionHelper s1r1(QDate(2004, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend), MyMoneyMoney(50.00), MyMoneyMoney(100.00), acStock1, QString(), acDividends); InvTransactionHelper s1r2(QDate(2004, 7, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::ReinvestDividend), MyMoneyMoney(50.00), MyMoneyMoney(80.00), acStock1, QString(), acDividends); InvTransactionHelper s1c1(QDate(2004, 8, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend), MyMoneyMoney(10.00), MyMoneyMoney(100.00), acStock1, acChecking, acDividends); InvTransactionHelper s1c2(QDate(2004, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Dividend), MyMoneyMoney(10.00), MyMoneyMoney(120.00), acStock1, acChecking, acDividends); InvTransactionHelper s1y1(QDate(2004, 9, 15), MyMoneySplit::actionName(eMyMoney::Split::Action::Yield), MyMoneyMoney(10.00), MyMoneyMoney(110.00), acStock1, acChecking, acInterest); makeEquityPrice(eqStock1, QDate(2004, 10, 1), MyMoneyMoney(100.00)); makeEquityPrice(eqStock3, QDate(2004, 10, 1), MyMoneyMoney(110.00)); makeEquityPrice(eqStock4, QDate(2004, 10, 1), MyMoneyMoney(110.00)); // // Investment Transactions Report // MyMoneyReport invtran_r( MyMoneyReport::eTopAccount, MyMoneyReport::eQCaction | MyMoneyReport::eQCshares | MyMoneyReport::eQCprice, eMyMoney::TransactionFilter::Date::UserDefined, MyMoneyReport::eDetailAll, i18n("Investment Transactions"), i18n("Test Report") ); invtran_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 12, 31)); invtran_r.setInvestmentsOnly(true); XMLandback(invtran_r); QueryTable invtran(invtran_r); #if 1 writeTabletoHTML(invtran, "investment_transactions_test.html"); QList rows = invtran.rows(); QVERIFY(rows.count() == 32); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == MyMoneyMoney(-100000.00)); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == MyMoneyMoney(-110000.00)); QVERIFY(MyMoneyMoney(rows[3][ListTable::ctValue]) == MyMoneyMoney(24000.00)); QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == MyMoneyMoney(20000.00)); QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == MyMoneyMoney(5000.00)); QVERIFY(MyMoneyMoney(rows[6][ListTable::ctValue]) == MyMoneyMoney(4000.00)); QVERIFY(MyMoneyMoney(rows[19][ListTable::ctValue]) == MyMoneyMoney(-50100.00)); QVERIFY(MyMoneyMoney(rows[22][ListTable::ctValue]) == MyMoneyMoney(-45100.00)); // need to fix these... fundamentally different from the original test //QVERIFY(MyMoneyMoney(invtran.m_rows[8][ListTable::ctValue])==MyMoneyMoney( -1000.00)); //QVERIFY(MyMoneyMoney(invtran.m_rows[11][ListTable::ctValue])==MyMoneyMoney( -1200.00)); //QVERIFY(MyMoneyMoney(invtran.m_rows[14][ListTable::ctValue])==MyMoneyMoney( -1100.00)); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctPrice]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[3][ListTable::ctPrice]) == MyMoneyMoney(120.00)); QVERIFY(MyMoneyMoney(rows[5][ListTable::ctPrice]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[7][ListTable::ctPrice]) == MyMoneyMoney()); QVERIFY(MyMoneyMoney(rows[10][ListTable::ctPrice]) == MyMoneyMoney()); QVERIFY(MyMoneyMoney(rows[19][ListTable::ctPrice]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[22][ListTable::ctPrice]) == MyMoneyMoney(90.00)); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctShares]) == MyMoneyMoney(1000.00)); QVERIFY(MyMoneyMoney(rows[4][ListTable::ctShares]) == MyMoneyMoney(-200.00)); QVERIFY(MyMoneyMoney(rows[6][ListTable::ctShares]) == MyMoneyMoney(50.00)); QVERIFY(MyMoneyMoney(rows[8][ListTable::ctShares]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[11][ListTable::ctShares]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[19][ListTable::ctShares]) == MyMoneyMoney(500.00)); QVERIFY(MyMoneyMoney(rows[22][ListTable::ctShares]) == MyMoneyMoney(500.00)); QVERIFY(rows[1][ListTable::ctAction] == "Buy"); QVERIFY(rows[3][ListTable::ctAction] == "Sell"); QVERIFY(rows[5][ListTable::ctAction] == "Reinvest"); QVERIFY(rows[7][ListTable::ctAction] == "Dividend"); QVERIFY(rows[13][ListTable::ctAction] == "Yield"); QVERIFY(rows[19][ListTable::ctAction] == "Buy"); QVERIFY(rows[22][ListTable::ctAction] == "Buy"); #else QVERIFY(rows.count() == 9); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctValue]) == MyMoneyMoney(100000.00)); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == MyMoneyMoney(110000.00)); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctValue]) == MyMoneyMoney(-24000.00)); QVERIFY(MyMoneyMoney(rows[3][ListTable::ctValue]) == MyMoneyMoney(-20000.00)); QVERIFY(MyMoneyMoney(rows[4][ListTable::ctValue]) == MyMoneyMoney(5000.00)); QVERIFY(MyMoneyMoney(rows[5][ListTable::ctValue]) == MyMoneyMoney(4000.00)); QVERIFY(MyMoneyMoney(rows[6][ListTable::ctValue]) == MyMoneyMoney(-1000.00)); QVERIFY(MyMoneyMoney(rows[7][ListTable::ctValue]) == MyMoneyMoney(-1200.00)); QVERIFY(MyMoneyMoney(rows[8][ListTable::ctValue]) == MyMoneyMoney(-1100.00)); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctPrice]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctPrice]) == MyMoneyMoney(120.00)); QVERIFY(MyMoneyMoney(rows[4][ListTable::ctPrice]) == MyMoneyMoney(100.00)); QVERIFY(MyMoneyMoney(rows[6][ListTable::ctPrice]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[8][ListTable::ctPrice]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctShares]) == MyMoneyMoney(1000.00)); QVERIFY(MyMoneyMoney(rows[3][ListTable::ctShares]) == MyMoneyMoney(-200.00)); QVERIFY(MyMoneyMoney(rows[5][ListTable::ctShares]) == MyMoneyMoney(50.00)); QVERIFY(MyMoneyMoney(rows[7][ListTable::ctShares]) == MyMoneyMoney(0.00)); QVERIFY(MyMoneyMoney(rows[8][ListTable::ctShares]) == MyMoneyMoney(0.00)); QVERIFY(rows[0][ListTable::ctAction] == "Buy"); QVERIFY(rows[2][ListTable::ctAction] == "Sell"); QVERIFY(rows[4][ListTable::ctAction] == "Reinvest"); QVERIFY(rows[6][ListTable::ctAction] == "Dividend"); QVERIFY(rows[8][ListTable::ctAction] == "Yield"); #endif #if 1 // i think this is the correct amount. different treatment of dividend and yield QVERIFY(MyMoneyMoney(rows[17][ListTable::ctValue]) == MyMoneyMoney(-153700.00)); QVERIFY(MyMoneyMoney(rows[29][ListTable::ctValue]) == MyMoneyMoney(24600.00)); QVERIFY(MyMoneyMoney(rows[31][ListTable::ctValue]) == MyMoneyMoney(-129100.00)); #else QVERIFY(searchHTML(html, i18n("Total Stock 1")) == MyMoneyMoney(171700.00)); QVERIFY(searchHTML(html, i18n("Grand Total")) == MyMoneyMoney(171700.00)); #endif // // Investment Performance Report // MyMoneyReport invhold_r( MyMoneyReport::eAccountByTopAccount, MyMoneyReport::eQCperformance, eMyMoney::TransactionFilter::Date::UserDefined, MyMoneyReport::eDetailAll, i18n("Investment Performance by Account"), i18n("Test Report") ); invhold_r.setDateFilter(QDate(2004, 1, 1), QDate(2004, 10, 1)); invhold_r.setInvestmentsOnly(true); XMLandback(invhold_r); QueryTable invhold(invhold_r); writeTabletoHTML(invhold, "Investment Performance by Account.html"); rows = invhold.rows(); QVERIFY(rows.count() == 5); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctReturn]) == MyMoneyMoney("669/10000")); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctReturnInvestment]) == MyMoneyMoney("-39/5000")); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctBuys]) == MyMoneyMoney(-210000.00)); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctSells]) == MyMoneyMoney(44000.00)); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctReinvestIncome]) == MyMoneyMoney(9000.00)); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctCashIncome]) == MyMoneyMoney(3300.00)); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctReturn]) == MyMoneyMoney("1349/10000")); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctReturnInvestment]) == MyMoneyMoney("1/10")); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctStartingBalance]) == MyMoneyMoney(100000.00)); // this should stay non-zero to check if investment performance is calculated at non-zero starting balance QVERIFY(MyMoneyMoney(rows[2][ListTable::ctReturn]) == MyMoneyMoney("2501/2500")); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctReturnInvestment]) == MyMoneyMoney("323/1250")); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctBuys]) == MyMoneyMoney(-95200.00)); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctSells]) == MyMoneyMoney(119800.00)); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctEndingBalance]) == MyMoneyMoney(0.00)); // this should stay zero to check if investment performance is calculated at zero ending balance QVERIFY(MyMoneyMoney(rows[4][ListTable::ctEndingBalance]) == MyMoneyMoney(280000.00)); #if 0 // Dump file & reports QFile g("investmentkmy.xml"); g.open(QIODevice::WriteOnly); MyMoneyStorageXML xml; - IMyMoneyStorageFormat& interface = xml; - interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); + IMyMoneyOperationsFormat& interface = xml; + interface.writeFile(&g, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); invtran.dump("invtran.html", "%1"); invhold.dump("invhold.html", "%1"); #endif } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } // prevents bug #312135 void QueryTableTest::testSplitShares() { try { MyMoneyMoney firstSharesPurchase(16); MyMoneyMoney splitFactor(2); MyMoneyMoney secondSharesPurchase(1); MyMoneyMoney sharesAtTheEnd = firstSharesPurchase / splitFactor + secondSharesPurchase; MyMoneyMoney priceBeforeSplit(74.99, 100); MyMoneyMoney priceAfterSplit = splitFactor * priceBeforeSplit; // Equities eqStock1 = makeEquity("Stock1", "STK1"); // Accounts acInvestment = makeAccount("Investment", eMyMoney::Account::Type::Investment, moZero, QDate(2017, 8, 1), acAsset); acStock1 = makeAccount("Stock 1", eMyMoney::Account::Type::Stock, moZero, QDate(2017, 8, 1), acInvestment, eqStock1); // Transactions // Date Action Shares Price Stock Asset Income InvTransactionHelper s1b1(QDate(2017, 8, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), firstSharesPurchase, priceBeforeSplit, acStock1, acChecking, QString()); InvTransactionHelper s1s1(QDate(2017, 8, 2), MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares), splitFactor, MyMoneyMoney(), acStock1, QString(), QString()); InvTransactionHelper s1b2(QDate(2017, 8, 3), MyMoneySplit::actionName(eMyMoney::Split::Action::BuyShares), secondSharesPurchase, priceAfterSplit, acStock1, acChecking, QString()); // // Investment Performance Report // MyMoneyReport invhold_r( MyMoneyReport::eAccountByTopAccount, MyMoneyReport::eQCperformance, eMyMoney::TransactionFilter::Date::UserDefined, MyMoneyReport::eDetailAll, i18n("Investment Performance by Account"), i18n("Test Report") ); invhold_r.setDateFilter(QDate(2017, 8, 1), QDate(2017, 8, 3)); invhold_r.setInvestmentsOnly(true); XMLandback(invhold_r); QueryTable invhold(invhold_r); writeTabletoHTML(invhold, "Investment Performance by Account (with stock split).html"); const auto rows = invhold.rows(); QVERIFY(rows.count() == 3); QVERIFY(MyMoneyMoney(rows[0][ListTable::ctBuys]) == sharesAtTheEnd * priceAfterSplit * MyMoneyMoney(-1)); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } // prevents bug #118159 void QueryTableTest::testConversionRate() { try { MyMoneyMoney firsConversionRate(1.1800, 10000); MyMoneyMoney secondConversionRate(1.1567, 10000); MyMoneyMoney amountWithdrawn(100); const auto acCadChecking = makeAccount(QString("Canadian Checking"), eMyMoney::Account::Type::Checkings, moZero, QDate(2017, 8, 1), acAsset, "CAD"); makePrice("CAD", QDate(2017, 8, 1), firsConversionRate); makePrice("CAD", QDate(2017, 8, 2), secondConversionRate); TransactionHelper t1(QDate(2017, 8, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), amountWithdrawn, acCadChecking, acSolo, "CAD"); MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAccount); filter.setDateFilter(QDate(2017, 8, 1), QDate(2017, 8, 2)); filter.setName("Transactions by Account"); auto cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl(filter); writeTabletoHTML(qtbl, "Transactions by Account (conversion rate).html"); const auto rows = qtbl.rows(); QVERIFY(rows.count() == 5); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctValue]) == amountWithdrawn * firsConversionRate * MyMoneyMoney(-1)); QVERIFY(MyMoneyMoney(rows[1][ListTable::ctPrice]) == firsConversionRate); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctBalance]) == amountWithdrawn * secondConversionRate * MyMoneyMoney(-1)); QVERIFY(MyMoneyMoney(rows[2][ListTable::ctPrice]) == secondConversionRate); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } //this is to prevent me from making mistakes again when modifying balances - asoliverez //this case tests only the opening and ending balance of the accounts void QueryTableTest::testBalanceColumn() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q1(QDate(2004, 3, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4y1(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t1q2(QDate(2004, 4, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q2(QDate(2004, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3q2(QDate(2004, 6, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4q2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); TransactionHelper t1y2(QDate(2005, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2y2(QDate(2005, 5, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acCredit, acParent); TransactionHelper t3y2(QDate(2005, 9, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent2, acCredit, acParent); TransactionHelper t4y2(QDate(2004, 11, 7), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moChild, acCredit, acChild); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance; filter.setQueryColumns(static_cast(cols)); // XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account.html"); QString html = qtbl_3.renderHTML(); QList rows = qtbl_3.rows(); QVERIFY(rows.count() == 19); //this is to make sure that the dates of closing and opening balances and the balance numbers are ok QString openingDate = QLocale().toString(QDate(2004, 1, 1), QLocale::ShortFormat); QString closingDate = QLocale().toString(QDate(2005, 9, 1), QLocale::ShortFormat); QVERIFY(html.indexOf(openingDate + "" + i18n("Opening Balance")) > 0); QVERIFY(html.indexOf(closingDate + "" + i18n("Closing Balance") + " -702.36") > 0); QVERIFY(html.indexOf(closingDate + "" + i18n("Closing Balance") + " -705.69") > 0); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testBalanceColumnWithMultipleCurrencies() { try { MyMoneyMoney moJpyOpening(0.0, 1); MyMoneyMoney moJpyPrice(0.010, 100); MyMoneyMoney moJpyPrice2(0.011, 100); MyMoneyMoney moJpyPrice3(0.024, 100); MyMoneyMoney moTransaction(100, 1); MyMoneyMoney moJpyTransaction(100, 1); QString acJpyChecking = makeAccount(QString("Japanese Checking"), eMyMoney::Account::Type::Checkings, moJpyOpening, QDate(2003, 11, 15), acAsset, "JPY"); makePrice("JPY", QDate(2004, 1, 1), MyMoneyMoney(moJpyPrice)); makePrice("JPY", QDate(2004, 5, 1), MyMoneyMoney(moJpyPrice2)); makePrice("JPY", QDate(2004, 6, 30), MyMoneyMoney(moJpyPrice3)); QDate openingDate(2004, 2, 20); QDate intermediateDate(2004, 5, 20); QDate closingDate(2004, 7, 20); TransactionHelper t1(openingDate, MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t4(openingDate, MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(moTransaction), acCredit, acChecking); TransactionHelper t2(intermediateDate, MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t5(intermediateDate, MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(moTransaction), acCredit, acChecking); TransactionHelper t3(closingDate, MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer), MyMoneyMoney(moJpyTransaction), acJpyChecking, acChecking, "JPY"); TransactionHelper t6(closingDate, MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit), MyMoneyMoney(moTransaction), acCredit, acChecking); // test that an income/expense transaction that involves a currency exchange is properly reported TransactionHelper t7(intermediateDate, MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), MyMoneyMoney(moJpyTransaction), acJpyChecking, acSolo, "JPY"); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eAccount); filter.setName("Transactions by Account"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory | MyMoneyReport::eQCbalance; filter.setQueryColumns(static_cast(cols)); // don't convert values to the default currency filter.setConvertCurrency(false); XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Transactions by Account (multiple currencies).html"); QString html = qtbl_3.renderHTML(); QList rows = qtbl_3.rows(); QVERIFY(rows.count() == 24); //this is to make sure that the dates of closing and opening balances and the balance numbers are ok QString openingDateString = QLocale().toString(openingDate, QLocale::ShortFormat); QString intermediateDateString = QLocale().toString(intermediateDate, QLocale::ShortFormat); QString closingDateString = QLocale().toString(closingDate, QLocale::ShortFormat); // check the opening and closing balances QVERIFY(html.indexOf(openingDateString + "" + i18n("Opening Balance") + "USD 0.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + "USD 304.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + "USD -300.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + "JPY -400.00") > 0); // after a transfer of 100 JPY the balance should be 1.00 - price is 0.010 (precision of 2) QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer from Japanese CheckingUSD 1.00USD 1.00") > 0); // after a transfer of 100 the balance should be 101.00 QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer from Credit CardUSD 100.00USD 101.00") > 0); // after a transfer of 100 JPY the balance should be 102.00 - price is 0.011 (precision of 2) QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer from Japanese CheckingUSD 1.00USD 102.00") > 0); // after a transfer of 100 the balance should be 202.00 QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer from Credit CardUSD 100.00USD 202.00") > 0); // after a transfer of 100 JPY the balance should be 204.00 - price is 0.024 (precision of 2) QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer from Japanese CheckingUSD 2.00USD 204.00") > 0); // after a transfer of 100 the balance should be 304.00 QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer from Credit CardUSD 100.00USD 304.00") > 0); // a 100.00 JPY withdrawal should be displayed as such even if the expense account uses another currency QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSoloJPY -100.00JPY -300.00") > 0); // now run the same report again but this time convert all values to the base currency and make sure the values are correct filter.setConvertCurrency(true); XMLandback(filter); QueryTable qtbl_4(filter); writeTabletoHTML(qtbl_4, "Transactions by Account (multiple currencies converted to base).html"); html = qtbl_4.renderHTML(); rows = qtbl_4.rows(); QVERIFY(rows.count() == 23); // check the opening and closing balances QVERIFY(html.indexOf(openingDateString + "" + i18n("Opening Balance") + " 0.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " 304.00") > 0); QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " -300.00") > 0); // although the balance should be -5.00 it's -8.00 because the foreign currency balance is converted using the closing date price (0.024) QVERIFY(html.indexOf(closingDateString + "" + i18n("Closing Balance") + " -8.00") > 0); // a 100.00 JPY transfer should be displayed as -1.00 when converted to the base currency using the opening date price QVERIFY(html.indexOf("" + openingDateString + "Test PayeeTransfer to Checking Account -1.00 -1.00") > 0); // a 100.00 JPY transfer should be displayed as -1.00 when converted to the base currency using the intermediate date price QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeTransfer to Checking Account -1.00 -2.00") > 0); // a 100.00 JPY transfer should be displayed as -2.00 when converted to the base currency using the closing date price (notice the balance is -5.00) QVERIFY(html.indexOf("" + closingDateString + "Test PayeeTransfer to Checking Account -2.00 -5.00") > 0); // a 100.00 JPY withdrawal should be displayed as -1.00 when converted to the base currency using the intermediate date price QVERIFY(html.indexOf("" + intermediateDateString + "Test PayeeSolo -1.00 -3.00") > 0); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } void QueryTableTest::testTaxReport() { try { TransactionHelper t1q1(QDate(2004, 1, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moSolo, acChecking, acSolo); TransactionHelper t2q1(QDate(2004, 2, 1), MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal), moParent1, acChecking, acTax); unsigned cols; MyMoneyReport filter; filter.setRowType(MyMoneyReport::eCategory); filter.setName("Tax Transactions"); cols = MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCaccount; filter.setQueryColumns(static_cast(cols)); filter.setTax(true); XMLandback(filter); QueryTable qtbl_3(filter); writeTabletoHTML(qtbl_3, "Tax Transactions.html"); QList rows = qtbl_3.rows(); QString html = qtbl_3.renderHTML(); QVERIFY(rows.count() == 5); } catch (const MyMoneyException &e) { QFAIL(qPrintable(e.what())); } } diff --git a/kmymoney/reports/tests/querytable-test.h b/kmymoney/reports/tests/querytable-test.h index 5f1789d63..f7661744e 100644 --- a/kmymoney/reports/tests/querytable-test.h +++ b/kmymoney/reports/tests/querytable-test.h @@ -1,47 +1,47 @@ /*************************************************************************** querytabletest.h ------------------- copyright : (C) 2002 by Thomas Baumgart email : ipwizard@users.sourceforge.net Ace Jones ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef QUERYTABLETEST_H #define QUERYTABLETEST_H #include #include "mymoneyfile.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" class QueryTableTest : public QObject { Q_OBJECT private: - MyMoneySeqAccessMgr* storage; + MyMoneyStorageMgr* storage; MyMoneyFile* file; private Q_SLOTS: void setup(); void init(); void cleanup(); void testQueryBasics(); void testCashFlowAnalysis(); void testAccountQuery(); void testInvestment(); void testSplitShares(); void testConversionRate(); void testBalanceColumn(); void testBalanceColumnWithMultipleCurrencies(); void testTaxReport(); }; #endif diff --git a/kmymoney/views/kmymoneyfile.cpp b/kmymoney/views/kmymoneyfile.cpp index 013696d53..1c0261ade 100644 --- a/kmymoney/views/kmymoneyfile.cpp +++ b/kmymoney/views/kmymoneyfile.cpp @@ -1,114 +1,114 @@ /*************************************************************************** kmymoneyfile.cpp - description ------------------- begin : Mon Jun 10 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /* * This file is currently not used anymore, but kept here for reference purposes */ #if 0 #include #include "kmymoneyfile.h" -#include "mymoneyseqaccessmgr.h" +#include "mymoneystoragemgr.h" KMyMoneyFile::KMyMoneyFile() { // m_file = MyMoneyFile::instance(); - m_storage = new MyMoneySeqAccessMgr; + m_storage = new MyMoneyStorageMgr; // m_file->attachStorage(m_storage); m_open = false; // lie a little bit for now } /* KMyMoneyFile::KMyMoneyFile(const QString&) { } */ KMyMoneyFile::~KMyMoneyFile() { if (m_storage) { MyMoneyFile::instance()->detachStorage(m_storage); delete m_storage; } // if(m_file) // delete m_file; } /* KMyMoneyFile *KMyMoneyFile::instance() { if (_instance == 0) { _instance = new KMyMoneyFile; } return _instance; } MyMoneyFile* KMyMoneyFile::file() { return m_file; } */ -MyMoneySeqAccessMgr* KMyMoneyFile::storage() +MyMoneyStorageMgr* KMyMoneyFile::storage() { return m_storage; } void KMyMoneyFile::reset() { /* delete m_storage; delete m_file; - m_storage = new MyMoneySeqAccessMgr; + m_storage = new MyMoneyStorageMgr; m_file = new MyMoneyFile(m_storage); */ } void KMyMoneyFile::open() { if (m_storage != 0) close(); - m_storage = new MyMoneySeqAccessMgr; + m_storage = new MyMoneyStorageMgr; MyMoneyFile::instance()->attachStorage(m_storage); m_open = true; } void KMyMoneyFile::close() { if (m_storage != 0) { MyMoneyFile::instance()->detachStorage(m_storage); delete m_storage; m_storage = 0; } m_open = false; } bool KMyMoneyFile::isOpen() { return m_open; } #endif diff --git a/kmymoney/views/kmymoneyfile.h b/kmymoney/views/kmymoneyfile.h index 809295d00..7d4a427ed 100644 --- a/kmymoney/views/kmymoneyfile.h +++ b/kmymoney/views/kmymoneyfile.h @@ -1,62 +1,62 @@ /*************************************************************************** kmymoneyfile.h - description ------------------- begin : Mon Jun 10 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEYFILE_H #define KMYMONEYFILE_H /* * This file is currently not used anymore, but kept here for reference purposes */ #if 0 #include "mymoneyaccount.h" -class MyMoneySeqAccessMgr; +class MyMoneyStorageMgr; /** *@author Michael Edwardes */ class KMyMoneyFile { private: // static KMyMoneyFile *_instance; // MyMoneyFile *m_file; - MyMoneySeqAccessMgr *m_storage; + MyMoneyStorageMgr *m_storage; bool m_open; protected: // KMyMoneyFile(const QString&); public: KMyMoneyFile(); ~KMyMoneyFile(); // static KMyMoneyFile *instance(); // MyMoneyFile* file(); - MyMoneySeqAccessMgr* storage(); + MyMoneyStorageMgr* storage(); void reset(); void open(); void close(); bool isOpen(); }; #endif #endif diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp index c4ed6e049..10ace8bf3 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,2305 +1,2311 @@ /*************************************************************************** kmymoneyview.cpp ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "kmymoneyview.h" // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #ifdef KF5Activities_FOUND #include #endif // ---------------------------------------------------------------------------- // Project Includes #ifdef ENABLE_UNFINISHEDFEATURES #include "simpleledgerview.h" #endif #include "kmymoneyglobalsettings.h" #include "kmymoneytitlelabel.h" #include #include "kcurrencyeditdlg.h" -#include "mymoneyseqaccessmgr.h" -#include "mymoneydatabasemgr.h" -#include "imymoneystorageformat.h" +#include "mymoneystoragemgr.h" #include "mymoneystoragebin.h" #include "mymoneyexception.h" #include "mymoneystoragexml.h" #include "mymoneystoragesql.h" #include "mymoneystorageanon.h" #include "khomeview.h" #include "kaccountsview.h" #include "kcategoriesview.h" #include "kinstitutionsview.h" #include "kpayeesview.h" #include "ktagsview.h" #include "kscheduledview.h" #include "kgloballedgerview.h" #include "kinvestmentview.h" #include "kreportsview.h" #include "kbudgetview.h" #include "kforecastview.h" #include "konlinejoboutbox.h" #include "kmymoney.h" #include "models.h" #include "accountsmodel.h" #include "equitiesmodel.h" #include "securitiesmodel.h" #include "icons.h" #include "amountedit.h" #include "kmymoneyaccounttreeview.h" #include "accountsviewproxymodel.h" #include "mymoneyprice.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneyaccount.h" #include "mymoneyinstitution.h" #include "kmymoneyedit.h" #include "mymoneyfile.h" #include "mymoneysecurity.h" #include "mymoneyreport.h" #include "mymoneyenums.h" using namespace Icons; using namespace eMyMoney; static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip; static constexpr char recoveryKeyId[] = "0xD2B08440"; typedef void(KMyMoneyView::*KMyMoneyViewFunc)(); KMyMoneyView::KMyMoneyView(KMyMoneyApp *kmymoney) : KPageWidget(nullptr), m_header(0), m_inConstructor(true), m_fileOpen(false), m_fmode(QFileDevice::ReadUser | QFileDevice::WriteUser), m_lastViewSelected(0) #ifdef KF5Activities_FOUND , m_activityResourceInstance(0) #endif { // this is a workaround for the bug in KPageWidget that causes the header to be shown // for a short while during page switch which causes a kind of bouncing of the page's // content and if the page's content is at it's minimum size then during a page switch // the main window's size is also increased to fit the header that is shown for a sort // period - reading the code in kpagewidget.cpp we know that the header should be at (1,1) // in a grid layout so if we find it there remove it for good to avoid the described issues QGridLayout* gridLayout = qobject_cast(layout()); if (gridLayout) { QLayoutItem* headerItem = gridLayout->itemAtPosition(1, 1); // make sure that we remove only the header - we avoid surprises if the header is not at (1,1) in the layout if (headerItem && qobject_cast(headerItem->widget()) != NULL) { gridLayout->removeItem(headerItem); // after we remove the KPageWidget standard header replace it with our own title label m_header = new KMyMoneyTitleLabel(this); m_header->setObjectName("titleLabel"); m_header->setMinimumSize(QSize(100, 30)); m_header->setRightImageFile("pics/titlelabel_background.png"); m_header->setVisible(KMyMoneyGlobalSettings::showTitleBar()); gridLayout->addWidget(m_header, 1, 1); } } newStorage(); m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit connect(kmymoney, &KMyMoneyApp::fileLoaded, this, &KMyMoneyView::slotRefreshViews); // Page 0 m_homeView = new KHomeView; viewFrames[View::Home] = m_model->addPage(m_homeView, i18n("Home")); viewFrames[View::Home]->setIcon(Icons::get(Icon::ViewHome)); connect(m_homeView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_homeView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 1 m_institutionsView = new KInstitutionsView; viewFrames[View::Institutions] = m_model->addPage(m_institutionsView, i18n("Institutions")); viewFrames[View::Institutions]->setIcon(Icons::get(Icon::ViewInstitutions)); connect(m_institutionsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_institutionsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 2 m_accountsView = new KAccountsView; viewFrames[View::Accounts] = m_model->addPage(m_accountsView, i18n("Accounts")); viewFrames[View::Accounts]->setIcon(Icons::get(Icon::ViewAccounts)); connect(m_accountsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_accountsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 3 m_scheduledView = new KScheduledView; //this is to solve the way long strings are handled differently among versions of KPageWidget viewFrames[View::Schedules] = m_model->addPage(m_scheduledView, i18n("Scheduled transactions")); viewFrames[View::Schedules]->setIcon(Icons::get(Icon::ViewSchedules)); connect(m_scheduledView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_scheduledView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 4 m_categoriesView = new KCategoriesView; viewFrames[View::Categories] = m_model->addPage(m_categoriesView, i18n("Categories")); viewFrames[View::Categories]->setIcon(Icons::get(Icon::ViewCategories)); connect(m_categoriesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_categoriesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 5 m_tagsView = new KTagsView; viewFrames[View::Tags] = m_model->addPage(m_tagsView, i18n("Tags")); viewFrames[View::Tags]->setIcon(Icons::get(Icon::ViewTags)); connect(m_tagsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_tagsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 6 m_payeesView = new KPayeesView; viewFrames[View::Payees] = m_model->addPage(m_payeesView, i18n("Payees")); viewFrames[View::Payees]->setIcon(Icons::get(Icon::ViewPayees)); connect(m_payeesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_payeesView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 7 m_ledgerView = new KGlobalLedgerView; viewFrames[View::Ledgers] = m_model->addPage(m_ledgerView, i18n("Ledgers")); viewFrames[View::Ledgers]->setIcon(Icons::get(Icon::ViewLedgers)); connect(m_ledgerView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_ledgerView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 8 m_investmentView = new KInvestmentView; viewFrames[View::Investments] = m_model->addPage(m_investmentView, i18n("Investments")); viewFrames[View::Investments]->setIcon(Icons::get(Icon::ViewInvestment)); connect(m_investmentView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_investmentView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 9 m_reportsView = new KReportsView; viewFrames[View::Reports] = m_model->addPage(m_reportsView, i18n("Reports")); viewFrames[View::Reports]->setIcon(Icons::get(Icon::ViewReports)); connect(m_reportsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_reportsView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 10 m_budgetView = new KBudgetView; viewFrames[View::Budget] = m_model->addPage(m_budgetView, i18n("Budgets")); viewFrames[View::Budget]->setIcon(Icons::get(Icon::ViewBudgets)); connect(m_budgetView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_budgetView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 11 m_forecastView = new KForecastView; viewFrames[View::Forecast] = m_model->addPage(m_forecastView, i18n("Forecast")); viewFrames[View::Forecast]->setIcon(Icons::get(Icon::ViewForecast)); connect(m_forecastView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_forecastView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); // Page 12 m_onlineJobOutboxView = new KOnlineJobOutbox; viewFrames[View::OnlineJobOutbox] = m_model->addPage(m_onlineJobOutboxView, i18n("Outbox")); viewFrames[View::OnlineJobOutbox]->setIcon(Icons::get(Icon::ViewOutbox)); connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::connectView); connect(m_onlineJobOutboxView, &KMyMoneyViewBase::aboutToShow, this, &KMyMoneyView::resetViewSelection); connect(m_reportsView, &KReportsView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); connect(m_ledgerView, &KGlobalLedgerView::switchViewRequested, this, &KMyMoneyView::slotSwitchView); connect(m_homeView, &KHomeView::ledgerSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); #ifdef ENABLE_UNFINISHEDFEATURES SimpleLedgerView* view = new SimpleLedgerView(kmymoney, this); KPageWidgetItem* frame = m_model->addPage(view, i18n("New ledger")); frame->setIcon(Icons::get(Icon::DocumentProperties)); #endif //set the model setModel(m_model); setCurrentPage(viewFrames[View::Home]); connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); updateViewType(); m_inConstructor = false; // add fast switching of main views through Ctrl + NUM_X struct pageInfo { View view; KMyMoneyViewFunc callback; QString text; QKeySequence shortcut = QKeySequence(); }; const QVector pageInfos { {View::Home, &KMyMoneyView::slotShowHomePage, i18n("Show home page"), Qt::CTRL + Qt::Key_1}, {View::Institutions, &KMyMoneyView::slotShowInstitutionsPage, i18n("Show institutions page"), Qt::CTRL + Qt::Key_2}, {View::Accounts, &KMyMoneyView::slotShowAccountsPage, i18n("Show accounts page"), Qt::CTRL + Qt::Key_3}, {View::Schedules, &KMyMoneyView::slotShowSchedulesPage, i18n("Show scheduled transactions page"), Qt::CTRL + Qt::Key_4}, {View::Categories, &KMyMoneyView::slotShowCategoriesPage, i18n("Show categories page"), Qt::CTRL + Qt::Key_5}, {View::Tags, &KMyMoneyView::slotShowTagsPage, i18n("Show tags page"), }, {View::Payees, &KMyMoneyView::slotShowPayeesPage, i18n("Show payees page"), Qt::CTRL + Qt::Key_6}, {View::Ledgers, &KMyMoneyView::slotShowLedgersPage, i18n("Show ledgers page"), Qt::CTRL + Qt::Key_7}, {View::Investments, &KMyMoneyView::slotShowInvestmentsPage, i18n("Show investments page"), Qt::CTRL + Qt::Key_8}, {View::Reports, &KMyMoneyView::slotShowReportsPage, i18n("Show reports page"), Qt::CTRL + Qt::Key_9}, {View::Budget, &KMyMoneyView::slotShowBudgetPage, i18n("Show budget page"), }, {View::Forecast, &KMyMoneyView::slotShowForecastPage, i18n("Show forecast page"), }, {View::OnlineJobOutbox, &KMyMoneyView::slotShowOutboxPage, i18n("Show outbox page") } }; QHash lutActions; auto aC = kmymoney->actionCollection(); auto pageCount = 0; foreach (const pageInfo info, pageInfos) { auto a = new QAction(this); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(QLatin1String("ShowPage") + QString::number(pageCount++)); a->setText(info.text); connect(a, &QAction::triggered, this, info.callback); lutActions.insert(info.view, a); // store QAction's pointer for later processing if (!info.shortcut.isEmpty()) aC->setDefaultShortcut(a, info.shortcut); } aC->addActions(lutActions.values()); // Initialize kactivities resource instance #ifdef KF5Activities_FOUND m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId(), this); connect(kmymoney, SIGNAL(fileLoaded(QUrl)), m_activityResourceInstance, SLOT(setUri(QUrl))); #endif } KMyMoneyView::~KMyMoneyView() { KMyMoneyGlobalSettings::setLastViewSelected(m_lastViewSelected); #ifdef KF5Activities_FOUND delete m_activityResourceInstance; #endif removeStorage(); } void KMyMoneyView::slotShowHomePage() { showPage(viewFrames[View::Home]); } void KMyMoneyView::slotShowInstitutionsPage() { showPage(viewFrames[View::Institutions]); m_institutionsView->setDefaultFocus(); } void KMyMoneyView::slotShowAccountsPage() { showPage(viewFrames[View::Accounts]); m_accountsView->setDefaultFocus(); } void KMyMoneyView::slotShowSchedulesPage() { showPage(viewFrames[View::Schedules]); m_scheduledView->setDefaultFocus(); } void KMyMoneyView::slotShowCategoriesPage() { showPage(viewFrames[View::Categories]); m_categoriesView->setDefaultFocus(); } void KMyMoneyView::slotShowTagsPage() { showPage(viewFrames[View::Tags]); m_tagsView->setDefaultFocus(); } void KMyMoneyView::slotShowPayeesPage() { showPage(viewFrames[View::Payees]); m_payeesView->setDefaultFocus(); } void KMyMoneyView::slotShowLedgersPage() { showPage(viewFrames[View::Ledgers]); m_ledgerView->setDefaultFocus(); } void KMyMoneyView::slotShowInvestmentsPage() { showPage(viewFrames[View::Investments]); m_investmentView->setDefaultFocus(); } void KMyMoneyView::slotShowReportsPage() { showPage(viewFrames[View::Reports]); m_reportsView->setDefaultFocus(); } void KMyMoneyView::slotShowBudgetPage() { showPage(viewFrames[View::Budget]); m_budgetView->setDefaultFocus(); } void KMyMoneyView::slotShowForecastPage() { showPage(viewFrames[View::Forecast]); m_forecastView->setDefaultFocus(); } void KMyMoneyView::slotShowOutboxPage() { showPage(viewFrames[View::OnlineJobOutbox]); m_onlineJobOutboxView->setDefaultFocus(); } void KMyMoneyView::showTitleBar(bool show) { if (m_header) m_header->setVisible(show); } void KMyMoneyView::updateViewType() { // set the face type KPageView::FaceType faceType = KPageView::List; switch (KMyMoneySettings::viewType()) { case 0: faceType = KPageView::List; break; case 1: faceType = KPageView::Tree; break; case 2: faceType = KPageView::Tabbed; break; } if (faceType != KMyMoneyView::faceType()) { setFaceType(faceType); if (faceType == KPageView::Tree) { QList views = findChildren(); foreach (QTreeView * view, views) { if (view && (view->parent() == this)) { view->setRootIsDecorated(false); break; } } } } } void KMyMoneyView::slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show) { QVector proxyModels {m_institutionsView->getProxyModel(), m_accountsView->getProxyModel(), m_categoriesView->getProxyModel(), m_budgetView->getProxyModel()}; for (auto i = proxyModels.count() - 1; i >= 0; --i) { // weed out unloaded views if (!proxyModels.at(i)) proxyModels.removeAt(i); } QString question; if (show) question = i18n("Do you want to show %1 column on every loaded view?", AccountsModel::getHeaderName(column)); else question = i18n("Do you want to hide %1 column on every loaded view?", AccountsModel::getHeaderName(column)); if (proxyModels.count() == 1 || // no need to ask what to do with other views because they aren't loaded KMessageBox::questionYesNo(this, question, QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("ShowColumnOnEveryView")) == KMessageBox::Yes) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); foreach(AccountsViewProxyModel *proxyModel, proxyModels) { if (!proxyModel) continue; proxyModel->setColumnVisibility(column, show); proxyModel->invalidate(); } } else if(show) { // in case we need to show it, we have to make sure to set the visibility // in the base model as well. Otherwise, we don't see the column through the proxy model Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); } } void KMyMoneyView::setOnlinePlugins(QMap& plugins) { m_accountsView->setOnlinePlugins(plugins); m_onlineJobOutboxView->setOnlinePlugins(plugins); } eDialogs::ScheduleResultCode KMyMoneyView::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { return m_scheduledView->enterSchedule(schedule, autoEnter, extendedKeys); } bool KMyMoneyView::showPageHeader() const { return false; } void KMyMoneyView::slotSwitchView(View view) { showPage(viewFrames[view]); } void KMyMoneyView::showPage(KPageWidgetItem* pageItem) { // reset all selected items before showing the selected view // but not while we're in our own constructor if (!m_inConstructor && pageItem != currentPage()) { kmymoney->slotResetSelections(); } // pretend we're in the constructor to avoid calling the // above resets. For some reason which I don't know the details // of, KJanusWidget::showPage() calls itself recursively. This // screws up the action handling, as items could have been selected // in the meantime. We prevent this by setting the m_inConstructor // to true and reset it to the previos value when we leave this method. bool prevConstructor = m_inConstructor; m_inConstructor = true; setCurrentPage(pageItem); m_inConstructor = prevConstructor; if (!m_inConstructor) { // fixup some actions that are dependant on the view // this does not work during construction kmymoney->slotUpdateActions(); } } bool KMyMoneyView::canPrint() { bool rc = ( viewFrames[View::Reports] == currentPage() || viewFrames[View::Home] == currentPage() ); return rc; } -void KMyMoneyView::newStorage(storageTypeE t) +void KMyMoneyView::newStorage() { removeStorage(); auto file = MyMoneyFile::instance(); - if (t == Memory) - file->attachStorage(new MyMoneySeqAccessMgr); - else - file->attachStorage(new MyMoneyDatabaseMgr); - + file->attachStorage(new MyMoneyStorageMgr); } void KMyMoneyView::removeStorage() { auto file = MyMoneyFile::instance(); - IMyMoneyStorage* p = file->storage(); - if (p != 0) { + auto p = file->storage(); + if (p) { file->detachStorage(p); delete p; } } void KMyMoneyView::enableViewsIfFileOpen() { // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this if (viewFrames[View::Accounts]->isEnabled() != m_fileOpen) viewFrames[View::Accounts]->setEnabled(m_fileOpen); if (viewFrames[View::Institutions]->isEnabled() != m_fileOpen) viewFrames[View::Institutions]->setEnabled(m_fileOpen); if (viewFrames[View::Schedules]->isEnabled() != m_fileOpen) viewFrames[View::Schedules]->setEnabled(m_fileOpen); if (viewFrames[View::Categories]->isEnabled() != m_fileOpen) viewFrames[View::Categories]->setEnabled(m_fileOpen); if (viewFrames[View::Payees]->isEnabled() != m_fileOpen) viewFrames[View::Payees]->setEnabled(m_fileOpen); if (viewFrames[View::Tags]->isEnabled() != m_fileOpen) viewFrames[View::Tags]->setEnabled(m_fileOpen); if (viewFrames[View::Budget]->isEnabled() != m_fileOpen) viewFrames[View::Budget]->setEnabled(m_fileOpen); if (viewFrames[View::Ledgers]->isEnabled() != m_fileOpen) viewFrames[View::Ledgers]->setEnabled(m_fileOpen); if (viewFrames[View::Investments]->isEnabled() != m_fileOpen) viewFrames[View::Investments]->setEnabled(m_fileOpen); if (viewFrames[View::Reports]->isEnabled() != m_fileOpen) viewFrames[View::Reports]->setEnabled(m_fileOpen); if (viewFrames[View::Forecast]->isEnabled() != m_fileOpen) viewFrames[View::Forecast]->setEnabled(m_fileOpen); if (viewFrames[View::OnlineJobOutbox]->isEnabled() != m_fileOpen) viewFrames[View::OnlineJobOutbox]->setEnabled(m_fileOpen); emit viewStateChanged(m_fileOpen); } void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) { showPage(viewFrames[View::Payees]); m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); } void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) { showPage(viewFrames[View::Tags]); m_tagsView->slotSelectTagAndTransaction(tag, account, transaction); } bool KMyMoneyView::fileOpen() { return m_fileOpen; } void KMyMoneyView::closeFile() { if (m_reportsView) m_reportsView->slotCloseAll(); // disconnect the signals disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::refresh); // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) Models::instance()->fileClosed(); emit kmmFilePlugin(preClose); if (isDatabase()) MyMoneyFile::instance()->storage()->close(); // to log off a database user newStorage(); slotShowHomePage(); emit kmmFilePlugin(postClose); m_fileOpen = false; emit fileClosed(); } void KMyMoneyView::ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } -bool KMyMoneyView::readFile(const QUrl &url, IMyMoneyStorageFormat* pExtReader) +bool KMyMoneyView::readFile(const QUrl &url, IMyMoneyOperationsFormat* pExtReader) { QString filename; bool downloadedFile = false; m_fileOpen = false; bool isEncrypted = false; - IMyMoneyStorageFormat* pReader = 0; + IMyMoneyOperationsFormat* pReader = 0; if (!url.isValid()) { qDebug("Invalid URL '%s'", qPrintable(url.url())); return false; } // disconnect the current storga manager from the engine MyMoneyFile::instance()->detachStorage(); if (url.scheme() == QLatin1String("sql")) { // handle reading of database m_fileType = KmmDb; // get rid of the mode parameter which is now redundant QUrl newUrl(url); QUrlQuery query(url); query.removeQueryItem("mode"); newUrl.setQuery(query); return (openDatabase(newUrl)); // on error, any message will have been displayed } - IMyMoneyStorage *storage = new MyMoneySeqAccessMgr; + auto storage = new MyMoneyStorageMgr; if (url.isLocalFile()) { filename = url.toLocalFile(); } else { downloadedFile = true; KIO::StoredTransferJob *transferjob = KIO::storedGet (url); KJobWidgets::setWindow(transferjob, this); if (! transferjob->exec()) { KMessageBox::detailedError(this, i18n("Error while loading file '%1'.", url.url()), transferjob->errorString(), i18n("File access error")); return false; } QTemporaryFile file; file.setAutoRemove(false); file.open(); file.write(transferjob->data()); filename = file.fileName(); file.close(); } // let's glimps into the file to figure out, if it's one // of the old (uncompressed) or new (compressed) files. QFile file(filename); QFileInfo info(file); if (!info.isFile()) { QString msg = i18n("

%1 is not a KMyMoney file.

", filename); KMessageBox::error(this, msg, i18n("Filetype Error")); return false; } m_fmode = QFileDevice::ReadUser | QFileDevice::WriteUser; m_fmode |= info.permissions(); bool rc = true; // There's a problem with the KFilterDev and KGPGFile classes: // One supports the at(n) member but not ungetch() together with // read() and the other does not provide an at(n) method but // supports read() that considers the ungetch() buffer. QFile // supports everything so this is not a problem. We solve the problem // for now by keeping track of which method can be used. bool haveAt = true; emit kmmFilePlugin(preOpen); if (file.open(QIODevice::ReadOnly)) { QByteArray hdr(2, '\0'); int cnt; cnt = file.read(hdr.data(), 2); file.close(); if (cnt == 2) { QIODevice* qfile = nullptr; if (QString(hdr) == QString("\037\213")) { // gzipped? qfile = new KCompressionDevice(filename, COMPRESSION_TYPE); } else if (QString(hdr) == QString("--") // PGP ASCII armored? || QString(hdr) == QString("\205\001") // PGP binary? || QString(hdr) == QString("\205\002")) { // PGP binary? if (KGPGFile::GPGAvailable()) { qfile = new KGPGFile(filename); haveAt = false; isEncrypted = true; } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("GPG is not available for decryption of file %1", filename))); qfile = new QFile(file.fileName()); } } else { // we can't use file directly, as we delete qfile later on qfile = new QFile(file.fileName()); } if (qfile->open(QIODevice::ReadOnly)) { try { hdr.resize(8); if (qfile->read(hdr.data(), 8) == 8) { if (haveAt) qfile->seek(0); else ungetString(qfile, hdr.data(), 8); // Ok, we got the first block of 8 bytes. Read in the two // unsigned long int's by preserving endianess. This is // achieved by reading them through a QDataStream object qint32 magic0, magic1; QDataStream s(&hdr, QIODevice::ReadOnly); s >> magic0; s >> magic1; // If both magic numbers match (we actually read in the // text 'KMyMoney' then we assume a binary file and // construct a reader for it. Otherwise, we construct // an XML reader object. // // The expression magic0 < 30 is only used to create // a binary reader if we assume an old binary file. This // should be removed at some point. An alternative is to // check the beginning of the file against an pattern // of the XML file (e.g. '?read(hdr.data(), 70) == 70) { if (haveAt) qfile->seek(0); else ungetString(qfile, hdr.data(), 70); QRegExp kmyexp(""); QRegExp gncexp("attachStorage(storage); loadAllCurrencies(); // currency list required for gnc MyMoneyFile::instance()->detachStorage(storage); pReader = pExtReader; m_fileType = GncXML; } } } } if (pReader) { pReader->setProgressCallback(&KMyMoneyView::progressCallback); - pReader->readFile(qfile, dynamic_cast(storage)); + pReader->readFile(qfile, storage); } else { if (m_fileType == KmmBinary) { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.", filename))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 contains an unknown file format.", filename))); } rc = false; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot read from file %1.", filename))); rc = false; } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot load file %1. Reason: %2", filename, e.what()))); rc = false; } if (pReader) { pReader->setProgressCallback(0); delete pReader; } qfile->close(); } else { KGPGFile *gpgFile = qobject_cast(qfile); if (gpgFile && !gpgFile->errorToString().isEmpty()) { KMessageBox::sorry(this, QString("%1"). arg(i18n("The following error was encountered while decrypting file %1: %2", filename, gpgFile->errorToString()))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); } rc = false; } delete qfile; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); rc = false; } // things are finished, now we connect the storage to the engine // which forces a reload of the cache in the engine with those // objects that are cached MyMoneyFile::instance()->attachStorage(storage); if (rc == false) return rc; // encapsulate transactions to the engine to be able to commit/rollback MyMoneyFileTransaction ft; // make sure we setup the encryption key correctly if (isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) { MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneyGlobalSettings::gpgRecipientList().join(",")); } // make sure we setup the name of the base accounts in translated form try { MyMoneyFile *file = MyMoneyFile::instance(); checkAccountName(file->asset(), i18n("Asset")); checkAccountName(file->liability(), i18n("Liability")); checkAccountName(file->income(), i18n("Income")); checkAccountName(file->expense(), i18n("Expense")); checkAccountName(file->equity(), i18n("Equity")); ft.commit(); } catch (const MyMoneyException &) { } // if a temporary file was downloaded, then it will be removed // with the next call. Otherwise, it stays untouched on the local // filesystem. if (downloadedFile) { QFile::remove(filename); } return initializeStorage(); } void KMyMoneyView::checkAccountName(const MyMoneyAccount& _acc, const QString& name) const { auto file = MyMoneyFile::instance(); if (_acc.name() != name) { MyMoneyAccount acc(_acc); acc.setName(name); file->modifyAccount(acc); } } bool KMyMoneyView::openDatabase(const QUrl &url) { m_fileOpen = false; // open the database - IMyMoneySerialize* pStorage = dynamic_cast(MyMoneyFile::instance()->storage()); - MyMoneyDatabaseMgr* pDBMgr = 0; - if (! pStorage) { - pDBMgr = new MyMoneyDatabaseMgr; - pStorage = dynamic_cast(pDBMgr); - } - QExplicitlySharedDataPointer reader = pStorage->connectToDatabase(url); + auto pStorage = MyMoneyFile::instance()->storage(); + if (!pStorage) + pStorage = new MyMoneyStorageMgr; + + auto reader = std::make_unique(pStorage, url); + QUrl dbURL(url); bool retry = true; while (retry) { switch (reader->open(dbURL, QIODevice::ReadWrite)) { case 0: // opened okay retry = false; break; case 1: // permanent error KMessageBox::detailedError(this, i18n("Cannot open database %1\n", dbURL.toDisplayString()), reader->lastError()); - if (pDBMgr) { + if (pStorage) { removeStorage(); - delete pDBMgr; + delete pStorage; } return false; case -1: // retryable error if (KMessageBox::warningYesNo(this, reader->lastError(), PACKAGE) == KMessageBox::No) { - if (pDBMgr) { + if (pStorage) { removeStorage(); - delete pDBMgr; + delete pStorage; } return false; } else { QUrlQuery query(dbURL); const QString optionKey = QLatin1String("options"); QString options = query.queryItemValue(optionKey); if(!options.isEmpty()) { options += QLatin1Char(','); } options += QLatin1String("override"); query.removeQueryItem(QLatin1String("mode")); query.removeQueryItem(optionKey); query.addQueryItem(optionKey, options); dbURL.setQuery(query); } } } - if (pDBMgr) { - removeStorage(); - MyMoneyFile::instance()->attachStorage(pDBMgr); - } // single user mode; read some of the data into memory // FIXME - readFile no longer relevant? // tried removing it but then got no indication that loading was complete // also, didn't show home page reader->setProgressCallback(&KMyMoneyView::progressCallback); if (!reader->readFile()) { - KMessageBox::detailedError(0, + KMessageBox::detailedError(this, i18n("An unrecoverable error occurred while reading the database"), reader->lastError().toLatin1(), i18n("Database malfunction")); return false; } + + if (pStorage) { + removeStorage(); + MyMoneyFile::instance()->attachStorage(pStorage); + } + m_fileOpen = true; reader->setProgressCallback(0); return initializeStorage(); } bool KMyMoneyView::initializeStorage() { bool blocked = MyMoneyFile::instance()->signalsBlocked(); MyMoneyFile::instance()->blockSignals(true); // we check, if we have any currency in the file. If not, we load // all the default currencies we know. MyMoneyFileTransaction ft; try { updateCurrencyNames(); ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } // make sure, we have a base currency and all accounts are // also assigned to a currency. QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug() << e.what(); } if (baseId.isEmpty()) { // Stay in this endless loop until we have a base currency, // as without it the application does not work anymore. while (baseId.isEmpty()) { selectBaseCurrency(); try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug() << e.what(); } } } else { // in some odd intermediate cases there could be files out there // that have a base currency set, but still have accounts that // do not have a base currency assigned. This call will take // care of it. We can safely remove it later. // // Another work-around for this scenario is to remove the base // currency setting from the XML file by removing the line // // // // and restart the application with this file. This will force to // run the above loop. selectBaseCurrency(); } // setup the standard precision AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); KSharedConfigPtr config = KSharedConfig::openConfig(); KPageWidgetItem* page; KConfigGroup grp = config->group("General Options"); if (KMyMoneyGlobalSettings::startLastViewSelected() != 0) page = viewFrames.value(static_cast(KMyMoneyGlobalSettings::lastViewSelected())); else page = viewFrames[View::Home]; // For debugging purposes, we can turn off the automatic fix manually // by setting the entry in kmymoneyrc to true grp = config->group("General Options"); if (grp.readEntry("SkipFix", false) != true) { MyMoneyFileTransaction ft; try { // Check if we have to modify the file before we allow to work with it - IMyMoneyStorage* s = MyMoneyFile::instance()->storage(); + auto s = MyMoneyFile::instance()->storage(); while (s->fileFixVersion() < s->currentFixVersion()) { qDebug("%s", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())))); switch (s->fileFixVersion()) { case 0: fixFile_0(); s->setFileFixVersion(1); break; case 1: fixFile_1(); s->setFileFixVersion(2); break; case 2: fixFile_2(); s->setFileFixVersion(3); break; case 3: fixFile_3(); s->setFileFixVersion(4); break; // add new levels above. Don't forget to increase currentFixVersion() for all // the storage backends this fix applies to default: throw MYMONEYEXCEPTION(i18n("Unknown fix level in input file")); } } ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } } else { qDebug("Skipping automatic transaction fix!"); } MyMoneyFile::instance()->blockSignals(blocked); // FIXME: we need to check, if it's necessary to have this // automatic funcitonality // if there's no asset account, then automatically start the // new account wizard // kmymoney->createInitialAccount(); m_fileOpen = true; emit kmmFilePlugin(postOpen); Models::instance()->fileOpened(); // connect the needed signals connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); // inform everyone about new data MyMoneyFile::instance()->preloadCache(); MyMoneyFile::instance()->forceDataChanged(); // views can wait since they are going to be refresed in slotRefreshViews connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::refresh); // if we currently see a different page, then select the right one if (page != currentPage()) { showPage(page); } emit fileOpened(); return true; } -void KMyMoneyView::saveToLocalFile(const QString& localFile, IMyMoneyStorageFormat* pWriter, bool plaintext, const QString& keyList) +void KMyMoneyView::saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList) { // Check GPG encryption bool encryptFile = true; bool encryptRecover = false; if (!keyList.isEmpty()) { if (!KGPGFile::GPGAvailable()) { KMessageBox::sorry(this, i18n("GPG does not seem to be installed on your system. Please make sure that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); encryptFile = false; } else { if (KMyMoneyGlobalSettings::encryptRecover()) { encryptRecover = true; if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { KMessageBox::sorry(this, i18n("

You have selected to encrypt your data also with the KMyMoney recover key, but the key with id

%1

has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the KMyMoney web-site. This time your data will not be encrypted with the KMyMoney recover key.

", QString(recoveryKeyId)), i18n("GPG Key not found")); encryptRecover = false; } } for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { if (!KGPGFile::keyAvailable(key)) { KMessageBox::sorry(this, i18n("

You have specified to encrypt your data for the user-id

%1.

Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.

", key), i18n("GPG Key not found")); encryptFile = false; break; } } if (encryptFile == true) { QString msg = i18n("

You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer No.

"); if (KMessageBox::questionYesNo(this, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { encryptFile = false; } } } } // Create a temporary file if needed QString writeFile = localFile; QTemporaryFile tmpFile; if (QFile::exists(localFile)) { tmpFile.open(); writeFile = tmpFile.fileName(); tmpFile.close(); } /** * @brief Automatically restore settings when scope is left */ struct restorePreviousSettingsHelper { restorePreviousSettingsHelper() : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()} { MyMoneyFile::instance()->blockSignals(true); } ~restorePreviousSettingsHelper() { MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); } const bool m_signalsWereBlocked; } restoreHelper; MyMoneyFileTransaction ft; MyMoneyFile::instance()->deletePair("kmm-encryption-key"); std::unique_ptr device; if (!keyList.isEmpty() && encryptFile && !plaintext) { std::unique_ptr kgpg = std::unique_ptr(new KGPGFile{writeFile}); if (kgpg) { for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { kgpg->addRecipient(key.toLatin1()); } if (encryptRecover) { kgpg->addRecipient(recoveryKeyId); } MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); device = std::unique_ptr(kgpg.release()); } } else { QFile *file = new QFile(writeFile); // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object device = std::unique_ptr(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); } ft.commit(); if (!device || !device->open(QIODevice::WriteOnly)) { throw MYMONEYEXCEPTION(i18n("Unable to open file '%1' for writing.", localFile)); } pWriter->setProgressCallback(&KMyMoneyView::progressCallback); - pWriter->writeFile(device.get(), dynamic_cast(MyMoneyFile::instance()->storage())); + pWriter->writeFile(device.get(), MyMoneyFile::instance()->storage()); device->close(); // Check for errors if possible, only possible for KGPGFile QFileDevice *fileDevice = qobject_cast(device.get()); if (fileDevice && fileDevice->error() != QFileDevice::NoError) { throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } if (writeFile != localFile) { // This simple comparison is possible because the strings are equal if no temporary file was created. // If a temporary file was created, it is made in a way that the name is definitely different. So no // symlinks etc. have to be evaluated. if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } QFile::setPermissions(localFile, m_fmode); pWriter->setProgressCallback(0); } bool KMyMoneyView::saveFile(const QUrl &url, const QString& keyList) { QString filename = url.path(); if (!fileOpen()) { KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); return false; } emit kmmFilePlugin(preSave); - std::unique_ptr storageWriter; + std::unique_ptr storageWriter; // If this file ends in ".ANON.XML" then this should be written using the // anonymous writer. bool plaintext = filename.right(4).toLower() == ".xml"; if (filename.right(9).toLower() == ".anon.xml") { //! @todo C++14: use std::make_unique, also some lines below - storageWriter = std::unique_ptr(new MyMoneyStorageANON); + storageWriter = std::unique_ptr(new MyMoneyStorageANON); } else { - storageWriter = std::unique_ptr(new MyMoneyStorageXML); + storageWriter = std::unique_ptr(new MyMoneyStorageXML); } // actually, url should be the parameter to this function // but for now, this would involve too many changes bool rc = true; try { if (! url.isValid()) { throw MYMONEYEXCEPTION(i18n("Malformed URL '%1'", url.url())); } if (url.isLocalFile()) { filename = url.toLocalFile(); try { const unsigned int nbak = KMyMoneyGlobalSettings::autoBackupCopies(); if (nbak) { KBackup::numberedBackupFile(filename, QString(), QString::fromLatin1("~"), nbak); } saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); } catch (const MyMoneyException &) { throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename)); } } else { QTemporaryFile tmpfile; tmpfile.open(); // to obtain the name tmpfile.close(); saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); Q_CONSTEXPR int permission = -1; QFile file(tmpfile.fileName()); file.open(QIODevice::ReadOnly); KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite); if (!putjob->exec()) { throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'.
%2", url.toDisplayString(), putjob->errorString())); } file.close(); } m_fileType = KmmXML; } catch (const MyMoneyException &e) { KMessageBox::error(this, e.what()); MyMoneyFile::instance()->setDirty(); rc = false; } emit kmmFilePlugin(postSave); return rc; } bool KMyMoneyView::saveAsDatabase(const QUrl &url) { - bool rc = false; - if (!fileOpen()) { - KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); - return (rc); - } - MyMoneyStorageSql *writer = new MyMoneyStorageSql(dynamic_cast(MyMoneyFile::instance()->storage()), url); + auto writer = new MyMoneyStorageSql(MyMoneyFile::instance()->storage(), url); bool canWrite = false; switch (writer->open(url, QIODevice::WriteOnly)) { case 0: canWrite = true; break; case -1: // dbase already has data, see if he wants to clear it out - if (KMessageBox::warningContinueCancel(0, + if (KMessageBox::warningContinueCancel(this, i18n("Database contains data which must be removed before using Save As.\n" "Do you wish to continue?"), "Database not empty") == KMessageBox::Continue) { if (writer->open(url, QIODevice::WriteOnly, true) == 0) canWrite = true; } else { delete writer; return false; } break; } + delete writer; if (canWrite) { - writer->setProgressCallback(&KMyMoneyView::progressCallback); - if (!writer->writeFile()) { - KMessageBox::detailedError(0, - i18n("An unrecoverable error occurred while writing to the database.\n" - "It may well be corrupt."), - writer->lastError().toLatin1(), - i18n("Database malfunction")); - rc = false; - } - writer->setProgressCallback(0); - rc = true; + saveDatabase(url); + return true; } else { KMessageBox::detailedError(this, i18n("Cannot open or create database %1.\n" "Retry Save As Database and click Help" " for further info.", url.toDisplayString()), writer->lastError()); + return false; + } +} + +bool KMyMoneyView::saveDatabase(const QUrl &url) +{ + auto rc = false; + if (!fileOpen()) { + KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); + return (rc); + } + auto writer = new MyMoneyStorageSql(MyMoneyFile::instance()->storage(), url); + writer->open(url, QIODevice::WriteOnly); + writer->setProgressCallback(&KMyMoneyView::progressCallback); + if (!writer->writeFile()) { + KMessageBox::detailedError(this, + i18n("An unrecoverable error occurred while writing to the database.\n" + "It may well be corrupt."), + writer->lastError().toLatin1(), + i18n("Database malfunction")); + rc = false; + } else { + rc = true; } + writer->setProgressCallback(0); delete writer; - return (rc); + return rc; } bool KMyMoneyView::dirty() { if (!fileOpen()) return false; return MyMoneyFile::instance()->dirty(); } void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) { Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); m_ledgerView->slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney()); } void KMyMoneyView::newFile() { closeFile(); m_fileType = KmmXML; // assume native type until saved m_fileOpen = true; } void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) { if (!baseCurrency.id().isEmpty()) { QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseCurrency.id() != baseId) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->setBaseCurrency(baseCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", baseCurrency.name(), e.what()), i18n("Set base currency")); } } AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction())); } } void KMyMoneyView::selectBaseCurrency() { auto file = MyMoneyFile::instance(); // check if we have a base currency. If not, we need to select one QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseId.isEmpty()) { QPointer dlg = new KCurrencyEditDlg(this); connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity))); dlg->exec(); delete dlg; } try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (!baseId.isEmpty()) { // check that all accounts have a currency QList list; file->accountList(list); QList::Iterator it; // don't forget those standard accounts list << file->asset(); list << file->liability(); list << file->income(); list << file->expense(); list << file->equity(); for (it = list.begin(); it != list.end(); ++it) { QString cid; try { if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0) cid = MyMoneyFile::instance()->currency((*it).currencyId()).id(); } catch (const MyMoneyException &) { } if (cid.isEmpty()) { (*it).setCurrencyId(baseId); MyMoneyFileTransaction ft; try { file->modifyAccount(*it); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), qPrintable(e.what())); } } } } } void KMyMoneyView::updateCurrencyNames() { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; QList storedCurrencies = MyMoneyFile::instance()->currencyList(); QList availableCurrencies = MyMoneyFile::instance()->availableCurrencyList(); QStringList currencyIDs; foreach (auto currency, availableCurrencies) currencyIDs.append(currency.id()); try { foreach (auto currency, storedCurrencies) { int i = currencyIDs.indexOf(currency.id()); if (i != -1 && availableCurrencies.at(i).name() != currency.name()) { currency.setName(availableCurrencies.at(i).name()); file->modifyCurrency(currency); } } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s updating currency names", qPrintable(e.what())); } } void KMyMoneyView::loadAllCurrencies() { auto file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; if (!file->currencyList().isEmpty()) return; QMap ancientCurrencies = file->ancientCurrencies(); try { foreach (auto currency, file->availableCurrencyList()) { file->addCurrency(currency); MyMoneyPrice price = ancientCurrencies.value(currency, MyMoneyPrice()); if (price != MyMoneyPrice()) file->addPrice(price); } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s loading currency", qPrintable(e.what())); } } void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) { if (viewFrames[View::Accounts] != currentPage()) showPage(viewFrames[View::Accounts]); m_accountsView->show(); } void KMyMoneyView::slotRefreshViews() { // turn off sync between ledger and investment view disconnect(m_investmentView, &KInvestmentView::accountSelected, m_ledgerView, static_cast(&KGlobalLedgerView::slotSelectAccount)); disconnect(m_ledgerView, &KGlobalLedgerView::objectSelected, m_investmentView, static_cast(&KInvestmentView::slotSelectAccount)); // TODO turn sync between ledger and investment view if selected by user if (KMyMoneyGlobalSettings::syncLedgerInvestment()) { connect(m_investmentView, &KInvestmentView::accountSelected, m_ledgerView, static_cast(&KGlobalLedgerView::slotSelectAccount)); connect(m_ledgerView, &KGlobalLedgerView::objectSelected, m_investmentView, static_cast(&KInvestmentView::slotSelectAccount)); } showTitleBar(KMyMoneyGlobalSettings::showTitleBar()); m_accountsView->refresh(); m_institutionsView->refresh(); m_categoriesView->refresh(); m_payeesView->refresh(); m_tagsView->refresh(); m_ledgerView->refresh(); m_budgetView->refresh(); m_homeView->refresh(); m_investmentView->refresh(); m_reportsView->refresh(); m_forecastView->refresh(); m_scheduledView->refresh(); m_payeesView->slotClosePayeeIdentifierSource(); } void KMyMoneyView::slotShowTransactionDetail(bool detailed) { KMyMoneyGlobalSettings::setShowRegisterDetailed(detailed); slotRefreshViews(); } void KMyMoneyView::progressCallback(int current, int total, const QString& msg) { kmymoney->progressCallback(current, total, msg); } void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex) { // remember the current page m_lastViewSelected = current.row(); // set the current page's title in the header if (m_header) m_header->setText(m_model->data(current, KPageModel::HeaderRole).toString()); } /* DO NOT ADD code to this function or any of it's called ones. Instead, create a new function, fixFile_n, and modify the initializeStorage() logic above to call it */ void KMyMoneyView::fixFile_3() { // make sure each storage object contains a (unique) id MyMoneyFile::instance()->storageId(); } void KMyMoneyView::fixFile_2() { auto file = MyMoneyFile::instance(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); // scan the transactions and modify transactions with two splits // which reference an account and a category to have the memo text // of the account. auto count = 0; foreach (const auto transaction, transactionList) { if (transaction.splitCount() == 2) { QString accountId; QString categoryId; QString accountMemo; QString categoryMemo; foreach (const auto split, transaction.splits()) { auto acc = file->account(split.accountId()); if (acc.isIncomeExpense()) { categoryId = split.id(); categoryMemo = split.memo(); } else { accountId = split.id(); accountMemo = split.memo(); } } if (!accountId.isEmpty() && !categoryId.isEmpty() && accountMemo != categoryMemo) { MyMoneyTransaction t(transaction); MyMoneySplit s(t.splitById(categoryId)); s.setMemo(accountMemo); t.modifySplit(s); file->modifyTransaction(t); ++count; } } } qDebug("%d transactions fixed in fixFile_2", count); } void KMyMoneyView::fixFile_1() { // we need to fix reports. If the account filter list contains // investment accounts, we need to add the stock accounts to the list // as well if we don't have the expert mode enabled if (!KMyMoneyGlobalSettings::expertMode()) { try { QList reports = MyMoneyFile::instance()->reportList(); QList::iterator it_r; for (it_r = reports.begin(); it_r != reports.end(); ++it_r) { QStringList list; (*it_r).accounts(list); QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } if (!missing.isEmpty()) { (*it_r).addAccount(missing); MyMoneyFile::instance()->modifyReport(*it_r); } } } catch (const MyMoneyException &) { } } } #if 0 if (!m_accountsView->allItemsSelected()) { // retrieve a list of selected accounts QStringList list; m_accountsView->selectedItems(list); // if we're not in expert mode, we need to make sure // that all stock accounts for the selected investment // account are also selected if (!KMyMoneyGlobalSettings::expertMode()) { QStringList missing; QStringList::const_iterator it_a, it_b; for (it_a = list.begin(); it_a != list.end(); ++it_a) { auto acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == Account::Type::Investment) { foreach (const auto accountID, acc.accountList()) { if (!list.contains(accountID)) { missing.append(accountID); } } } } list += missing; } m_filter.addAccount(list); } #endif void KMyMoneyView::fixFile_0() { /* (Ace) I am on a crusade against file fixups. Whenever we have to fix the * file, it is really a warning. So I'm going to print a debug warning, and * then go track them down when I see them to figure out how they got saved * out needing fixing anyway. */ auto file = MyMoneyFile::instance(); QList accountList; file->accountList(accountList); QList::Iterator it_a; QList scheduleList = file->scheduleList(); QList::Iterator it_s; MyMoneyAccount equity = file->equity(); MyMoneyAccount asset = file->asset(); bool equityListEmpty = equity.accountList().count() == 0; for (it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { if ((*it_a).accountType() == Account::Type::Loan || (*it_a).accountType() == Account::Type::AssetLoan) { fixLoanAccount_0(*it_a); } // until early before 0.8 release, the equity account was not saved to // the file. If we have an equity account with no sub-accounts but // find and equity account that has equity() as it's parent, we reparent // this account. Need to move it to asset() first, because otherwise // MyMoneyFile::reparent would act as NOP. if (equityListEmpty && (*it_a).accountType() == Account::Type::Equity) { if ((*it_a).parentAccountId() == equity.id()) { auto acc = *it_a; // tricky, force parent account to be empty so that we really // can re-parent it acc.setParentAccountId(QString()); file->reparentAccount(acc, equity); qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id(); } } } for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { fixSchedule_0(*it_s); } fixTransactions_0(); } void KMyMoneyView::fixSchedule_0(MyMoneySchedule sched) { MyMoneyTransaction t = sched.transaction(); QList splitList = t.splits(); QList::ConstIterator it_s; bool updated = false; try { // Check if the splits contain valid data and set it to // be valid. for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++it_s) { // the first split is always the account on which this transaction operates // and if the transaction commodity is not set, we take this if (it_s == splitList.constBegin() && t.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity"; try { auto acc = MyMoneyFile::instance()->account((*it_s).accountId()); t.setCommodity(acc.currencyId()); updated = true; } catch (const MyMoneyException &) { } } // make sure the account exists. If not, remove the split try { MyMoneyFile::instance()->account((*it_s).accountId()); } catch (const MyMoneyException &) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist."; t.removeSplit(*it_s); updated = true; } if ((*it_s).reconcileFlag() != eMyMoney::Split::State::NotReconciled) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; MyMoneySplit split = *it_s; split.setReconcileDate(QDate()); split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); t.modifySplit(split); updated = true; } // the schedule logic used to operate only on the value field. // This is now obsolete. if ((*it_s).shares().isZero() && !(*it_s).value().isZero()) { MyMoneySplit split = *it_s; split.setShares(split.value()); t.modifySplit(split); updated = true; } } // If there have been changes, update the schedule and // the engine data. if (updated) { sched.setTransaction(t); MyMoneyFile::instance()->modifySchedule(sched); } } catch (const MyMoneyException &e) { qWarning("Unable to update broken schedule: %s", qPrintable(e.what())); } } void KMyMoneyView::fixLoanAccount_0(MyMoneyAccount acc) { if (acc.value("final-payment").isEmpty() || acc.value("term").isEmpty() || acc.value("periodic-payment").isEmpty() || acc.value("loan-amount").isEmpty() || acc.value("interest-calculation").isEmpty() || acc.value("schedule").isEmpty() || acc.value("fixed-interest").isEmpty()) { KMessageBox::information(this, i18n("

The account \"%1\" was previously created as loan account but some information is missing.

The new loan wizard will be started to collect all relevant information.

Please use KMyMoney version 0.8.7 or later and earlier than version 0.9 to correct the problem.

" , acc.name()), i18n("Account problem")); throw MYMONEYEXCEPTION("Fix LoanAccount0 not supported anymore"); } } void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { MyMoneyFileTransaction ft; try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); } // now search the split that does not have an account reference // and set it up to be the one of the account we just added // to the account pool. Note: the schedule code used to leave // this always the first split, but the loan code leaves it as // the second one. So I thought, searching is a good alternative .... foreach (const auto split, t.splits()) { if (split.accountId().isEmpty()) { MyMoneySplit s = split; s.setAccountId(newAccount.id()); t.modifySplit(s); break; } } newSchedule.setTransaction(t); MyMoneyFile::instance()->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.isLoan()) { newAccount.setValue("schedule", newSchedule.id()); MyMoneyFile::instance()->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add schedule: %1", e.what())); } } } void KMyMoneyView::fixTransactions_0() { auto file = MyMoneyFile::instance(); QList scheduleList = file->scheduleList(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); QList::Iterator it_x; QStringList interestAccounts; KMSTATUS(i18n("Fix transactions")); kmymoney->slotStatusProgressBar(0, scheduleList.count() + transactionList.count()); int cnt = 0; // scan the schedules to find interest accounts for (it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) { MyMoneyTransaction t = (*it_x).transaction(); QList::ConstIterator it_s; QStringList accounts; bool hasDuplicateAccounts = false; foreach (const auto split, t.splits()) { if (accounts.contains(split.accountId())) { hasDuplicateAccounts = true; qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId(); } else { accounts << split.accountId(); } if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { if (interestAccounts.contains(split.accountId()) == 0) { interestAccounts << split.accountId(); } } } if (hasDuplicateAccounts) { fixDuplicateAccounts_0(t); } ++cnt; if (!(cnt % 10)) kmymoney->slotStatusProgressBar(cnt); } // scan the transactions and modify loan transactions for (auto& transaction : transactionList) { QString defaultAction; QList splits = transaction.splits(); QStringList accounts; // check if base commodity is set. if not, set baseCurrency if (transaction.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency"; transaction.setCommodity(file->baseCurrency().id()); file->modifyTransaction(transaction); } bool isLoan = false; // Determine default action if (transaction.splitCount() == 2) { // check for transfer int accountCount = 0; MyMoneyMoney val; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); if (acc.accountGroup() == Account::Type::Asset || acc.accountGroup() == Account::Type::Liability) { val = split.value(); accountCount++; if (acc.accountType() == Account::Type::Loan || acc.accountType() == Account::Type::AssetLoan) isLoan = true; } else break; } if (accountCount == 2) { if (isLoan) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer); } else { if (val.isNegative()) defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); else defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); } } isLoan = false; foreach (const auto split, splits) { auto acc = file->account(split.accountId()); MyMoneyMoney val = split.value(); if (acc.accountGroup() == Account::Type::Asset || acc.accountGroup() == Account::Type::Liability) { if (!val.isPositive()) { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal); break; } else { defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit); break; } } } #if 0 // Check for correct actions in transactions referencing credit cards bool needModify = false; // The action fields are actually not used anymore in the ledger view logic // so we might as well skip this whole thing here! for (it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) { auto acc = file->account((*it_s).accountId()); MyMoneyMoney val = (*it_s).value(); if (acc.accountType() == Account::Type::CreditCard) { if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer)) needModify = true; } } // (Ace) Extended the #endif down to cover this conditional, because as-written // it will ALWAYS be skipped. if (needModify == true) { for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { (*it_s).setAction(defaultAction); transaction.modifySplit(*it_s); file->modifyTransaction(transaction); } splits = transaction.splits(); // update local copy qDebug("Fixed credit card assignment in %s", transaction.id().data()); } #endif // Check for correct assignment of ActionInterest in all splits // and check if there are any duplicates in this transactions for (auto& split : splits) { MyMoneyAccount splitAccount = file->account(split.accountId()); if (!accounts.contains(split.accountId())) { accounts << split.accountId(); } // if this split references an interest account, the action // must be of type ActionInterest if (interestAccounts.contains(split.accountId())) { if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest"; split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } // if it does not reference an interest account, it must not be // of type ActionInterest } else { if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest"; split.setAction(defaultAction); transaction.modifySplit(split); file->modifyTransaction(transaction); qDebug("Fixed interest action in %s", qPrintable(transaction.id())); } } // check that for splits referencing an account that has // the same currency as the transactions commodity the value // and shares field are the same. if (transaction.commodity() == splitAccount.currencyId() && split.value() != split.shares()) { qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value"; split.setShares(split.value()); transaction.modifySplit(split); file->modifyTransaction(transaction); } // fix the shares and values to have the correct fraction if (!splitAccount.isInvest()) { try { int fract = splitAccount.fraction(); if (split.shares() != split.shares().convert(fract)) { qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id())); split.setShares(split.shares().convert(fract)); split.setValue(split.value().convert(fract)); transaction.modifySplit(split); file->modifyTransaction(transaction); } } catch (const MyMoneyException &) { qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId())); } } } ++cnt; if (!(cnt % 10)) kmymoney->slotStatusProgressBar(cnt); } kmymoney->slotStatusProgressBar(-1, -1); } void KMyMoneyView::fixDuplicateAccounts_0(MyMoneyTransaction& t) { qDebug("Duplicate account in transaction %s", qPrintable(t.id())); } void KMyMoneyView::slotPrintView() { if (viewFrames[View::Reports] == currentPage()) m_reportsView->slotPrintView(); else if (viewFrames[View::Home] == currentPage()) m_homeView->slotPrintView(); } void KMyMoneyView::resetViewSelection(const View) { emit aboutToChangeView(); } void KMyMoneyView::connectView(const View view) { KMyMoneyAccountTreeView *treeView; switch (view) { case View::Home: disconnect(m_homeView, &KHomeView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_homeView, &KHomeView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_homeView, &KHomeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); break; case View::Accounts: disconnect(m_accountsView, &KAccountsView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_accountsView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_accountsView, &KAccountsView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(Models::instance()->accountsModel(), &AccountsModel::netWorthChanged, m_accountsView, &KAccountsView::slotNetWorthChanged); break; case View::Schedules: disconnect(m_scheduledView, &KScheduledView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_scheduledView, &KScheduledView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(m_scheduledView, &KScheduledView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_scheduledView, &KScheduledView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); break; case View::Institutions: disconnect(m_institutionsView, &KInstitutionsView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_institutionsView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_institutionsView, &KInstitutionsView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(Models::instance()->institutionsModel(), &AccountsModel::netWorthChanged, m_institutionsView, &KInstitutionsView::slotNetWorthChanged); break; case View::Categories: disconnect(m_categoriesView, &KCategoriesView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_categoriesView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_categoriesView, &KCategoriesView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(Models::instance()->institutionsModel(), &AccountsModel::profitChanged, m_categoriesView, &KCategoriesView::slotProfitChanged); break; case View::Tags: disconnect(m_tagsView, &KTagsView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_tagsView, &KTagsView::transactionSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); break; case View::Payees: disconnect(m_payeesView, &KTagsView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_payeesView, &KPayeesView::transactionSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); break; case View::Ledgers: disconnect(m_ledgerView, &KGlobalLedgerView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_ledgerView, &KGlobalLedgerView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(m_ledgerView, &KGlobalLedgerView::openPayeeRequested, this, &KMyMoneyView::slotPayeeSelected); connect(m_ledgerView, &KGlobalLedgerView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_ledgerView, &KGlobalLedgerView::transactionsSelected, this, &KMyMoneyView::slotTransactionsSelected); connect(m_ledgerView, &KGlobalLedgerView::transactionsContextMenuRequested, this, &KMyMoneyView::slotTransactionsMenuRequested); connect(m_ledgerView, &KGlobalLedgerView::statusProgress, this, &KMyMoneyView::statusProgress); connect(m_ledgerView, &KGlobalLedgerView::statusMsg, this, &KMyMoneyView::statusMsg); connect(m_ledgerView, &KGlobalLedgerView::accountReconciled, this, &KMyMoneyView::accountReconciled); connect(m_ledgerView, &KGlobalLedgerView::enterOverdueSchedulesRequested, m_scheduledView, &KScheduledView::slotEnterOverdueSchedules); connect(m_scheduledView, &KScheduledView::enterOverdueSchedulesFinished, m_ledgerView, &KGlobalLedgerView::slotContinueReconciliation); break; case View::Budget: disconnect(m_budgetView, &KBudgetView::aboutToShow, this, &KMyMoneyView::connectView); treeView = m_budgetView->getTreeView(); connect(treeView, &KMyMoneyAccountTreeView::openObjectRequested, this, &KMyMoneyView::slotOpenObjectRequested); connect(treeView, &KMyMoneyAccountTreeView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); connect(treeView, &KMyMoneyAccountTreeView::columnToggled, this, &KMyMoneyView::slotAccountTreeViewChanged); connect(m_budgetView, &KBudgetView::objectSelected, this, &KMyMoneyView::slotObjectSelected); break; case View::Investments: disconnect(m_investmentView, &KInvestmentView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_investmentView, &KInvestmentView::accountSelected, kmymoney, &KMyMoneyApp::slotSelectAccount); connect(m_investmentView, &KInvestmentView::objectSelected, this, &KMyMoneyView::slotObjectSelected); connect(m_investmentView, &KInvestmentView::contextMenuRequested, this, &KMyMoneyView::slotContextMenuRequested); break; case View::Reports: disconnect(m_reportsView, &KReportsView::aboutToShow, this, &KMyMoneyView::connectView); connect(m_reportsView, &KReportsView::transactionSelected, m_ledgerView, &KGlobalLedgerView::slotLedgerSelected); break; case View::Forecast: disconnect(m_forecastView, &KForecastView::aboutToShow, this, &KMyMoneyView::connectView); break; case View::OnlineJobOutbox: disconnect(m_onlineJobOutboxView, &KOnlineJobOutbox::aboutToShow, this, &KMyMoneyView::connectView); break; default: break; } } void KMyMoneyView::slotOpenObjectRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); // check if we can open this account // currently it make's sense for asset and liability accounts if (!MyMoneyFile::instance()->isStandardAccount(acc.id())) m_ledgerView->slotLedgerSelected(acc.id(), QString()); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { // const auto& inst = static_cast(obj); m_institutionsView->slotEditInstitution(); } else if (typeid(obj) == typeid(MyMoneySchedule)) { m_scheduledView->slotEditSchedule(); } else if (typeid(obj) == typeid(MyMoneyReport)) { const auto& rep = static_cast(obj); m_reportsView->slotOpenReport(rep); } } void KMyMoneyView::slotObjectSelected(const MyMoneyObject& obj) { // carrying some slots over to views isn't easy for all slots... // ...so calls to kmymoney still must be here if (typeid(obj) == typeid(MyMoneyAccount)) { kmymoney->slotSelectAccount(obj); m_investmentView->updateActions(obj); m_categoriesView->updateActions(obj); m_accountsView->updateActions(obj); m_ledgerView->updateActions(obj); m_reportsView->updateActions(obj); m_onlineJobOutboxView->updateActions(obj); // for plugin only const auto& acc = static_cast(obj); if (!acc.isIncomeExpense() && !MyMoneyFile::instance()->isStandardAccount(acc.id())) emit accountSelected(acc); } else if (typeid(obj) == typeid(MyMoneyInstitution)) { m_institutionsView->updateActions(obj); } else if (typeid(obj) == typeid(MyMoneySchedule)) { kmymoney->slotUpdateActions(); m_scheduledView->updateActions(obj); } } void KMyMoneyView::slotContextMenuRequested(const MyMoneyObject& obj) { if (typeid(obj) == typeid(MyMoneyAccount)) { const auto& acc = static_cast(obj); if (acc.isInvest()) { m_investmentView->slotShowInvestmentMenu(acc); return; } else if (acc.isIncomeExpense()) { m_categoriesView->slotShowCategoriesMenu(acc); } else { m_accountsView->slotShowAccountMenu(acc); } } else if (typeid(obj) == typeid(MyMoneyInstitution)) { const auto& inst = static_cast(obj); m_institutionsView->slotShowInstitutionsMenu(inst); } else if (typeid(obj) == typeid(MyMoneySchedule)) { const auto& sch = static_cast(obj); m_scheduledView->slotShowScheduleMenu(sch); } } void KMyMoneyView::slotTransactionsMenuRequested(const KMyMoneyRegister::SelectedTransactions& list) { Q_UNUSED(list) m_ledgerView->slotShowTransactionMenu(MyMoneySplit()); } void KMyMoneyView::slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& list) { m_ledgerView->updateLedgerActions(list); emit transactionsSelected(list); // for plugins } diff --git a/kmymoney/views/kmymoneyview.h b/kmymoney/views/kmymoneyview.h index c5bb846bb..9289addd0 100644 --- a/kmymoney/views/kmymoneyview.h +++ b/kmymoney/views/kmymoneyview.h @@ -1,570 +1,571 @@ /*************************************************************************** kmymoneyview.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEYVIEW_H #define KMYMONEYVIEW_H #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "selectedtransactions.h" #ifdef KF5Activities_FOUND namespace KActivities { class ResourceInstance; } #endif namespace eAccountsModel { enum class Column; } namespace eMenu { enum class Action; } namespace KMyMoneyPlugin { class OnlinePlugin; } namespace eDialogs { enum class ScheduleResultCode; } class KMyMoneyApp; class KHomeView; class KAccountsView; class KCategoriesView; class KInstitutionsView; class KPayeesView; class KTagsView; class KBudgetView; class KScheduledView; class KGlobalLedgerView; -class IMyMoneyStorageFormat; +class IMyMoneyOperationsFormat; class MyMoneyTransaction; class KInvestmentView; class KReportsView; class MyMoneySchedule; class MyMoneySecurity; class MyMoneyReport; class TransactionEditor; class KForecastView; class KOnlineJobOutbox; class KMyMoneyTitleLabel; class MyMoneyAccount; class MyMoneyMoney; class MyMoneyObject; class QLabel; /** * This class represents the view of the MyMoneyFile which contains * Banks/Accounts/Transactions, Recurring transactions (or Bills & Deposits) * and scripts (yet to be implemented). Each different aspect of the file * is represented by a tab within the view. * * @author Michael Edwardes 2001 Copyright 2000-2001 * * @short Handles the view of the MyMoneyFile. */ enum class View; class KMyMoneyView : public KPageWidget { Q_OBJECT public: // file actions for plugin enum fileActions { preOpen, postOpen, preSave, postSave, preClose, postClose }; private: enum menuID { AccountNew = 1, AccountOpen, AccountReconcile, AccountEdit, AccountDelete, AccountOnlineMap, AccountOnlineUpdate, AccountOfxConnect, CategoryNew }; enum storageTypeE { Memory = 0, Database } _storageType; KPageWidgetModel* m_model; KHomeView *m_homeView; KAccountsView *m_accountsView; KInstitutionsView *m_institutionsView; KCategoriesView *m_categoriesView; KPayeesView *m_payeesView; KTagsView *m_tagsView; KBudgetView *m_budgetView; KScheduledView *m_scheduledView; KGlobalLedgerView *m_ledgerView; KInvestmentView *m_investmentView; KReportsView* m_reportsView; KForecastView* m_forecastView; KOnlineJobOutbox* m_onlineJobOutboxView; QHash viewFrames; KMyMoneyTitleLabel* m_header; bool m_inConstructor; bool m_fileOpen; QFileDevice::Permissions m_fmode; int m_lastViewSelected; // Keep a note of the file type typedef enum _fileTypeE { KmmBinary = 0, // native, binary KmmXML, // native, XML KmmDb, // SQL database /* insert new native file types above this line */ MaxNativeFileType, /* and non-native types below */ GncXML // Gnucash XML } fileTypeE; fileTypeE m_fileType; #ifdef KF5Activities_FOUND private: KActivities::ResourceInstance * m_activityResourceInstance; #endif private: void ungetString(QIODevice *qfile, char * buf, int len); /** * if no base currency is defined, start the dialog and force it to be set */ void selectBaseCurrency(); /** * This method attaches an empty storage object to the MyMoneyFile * object. It calls removeStorage() to remove a possibly attached * storage object. */ - void newStorage(storageTypeE = Memory); + void newStorage(); /** * This method removes an attached storage from the MyMoneyFile * object. */ void removeStorage(); void viewAccountList(const QString& selectAccount); // Show the accounts view static void progressCallback(int current, int total, const QString&); /** */ void fixFile_0(); void fixFile_1(); void fixFile_2(); void fixFile_3(); /** */ void fixLoanAccount_0(MyMoneyAccount acc); /** */ void fixTransactions_0(); void fixSchedule_0(MyMoneySchedule sched); void fixDuplicateAccounts_0(MyMoneyTransaction& t); void createSchedule(MyMoneySchedule s, MyMoneyAccount& a); void checkAccountName(const MyMoneyAccount& acc, const QString& name) const; public: /** * The constructor for KMyMoneyView. Just creates all the tabs for the * different aspects of the MyMoneyFile. */ explicit KMyMoneyView(KMyMoneyApp *kmymoney); /** * Destructor */ ~KMyMoneyView(); /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ bool fileOpen(); /** * Closes the open MyMoneyFile and frees all the allocated memory, I hope ! */ void closeFile(); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ - bool readFile(const QUrl &url, IMyMoneyStorageFormat *pExtReader = nullptr); + bool readFile(const QUrl &url, IMyMoneyOperationsFormat *pExtReader = nullptr); /** * Saves the data into permanent storage using the XML format. * * @param url The URL to save into. * If no protocol is specified, file:// is assumed. * @param keyList QString containing a comma separated list of keys * to be used for encryption. If @p keyList is empty, * the file will be saved unencrypted (the default) * * @retval false save operation failed * @retval true save operation was successful */ bool saveFile(const QUrl &url, const QString& keyList = QString()); /** * Saves the data into permanent storage on a new or empty SQL database. * * @param url The pseudo of tyhe database * * @retval false save operation failed * @retval true save operation was successful */ //const bool saveDatabase(const QUrl &url); This no longer relevant /** * Saves the data into permanent storage on a new or empty SQL database. * * @param url The pseudo URL of the database * * @retval false save operation failed * @retval true save operation was successful */ bool saveAsDatabase(const QUrl &url); + bool saveDatabase(const QUrl &url); /** * Call this to find out if the currently open file is native KMM * * @retval true file is native * @retval false file is foreign */ bool isNativeFile() { return (m_fileOpen && (m_fileType < MaxNativeFileType)); } /** * Call this to find out if the currently open file is a sql database * * @retval true file is database * @retval false file is serial */ bool isDatabase() { return (m_fileOpen && ((m_fileType == KmmDb))); } /** * Call this to see if the MyMoneyFile contains any unsaved data. * * @retval true if any data has been modified but not saved * @retval false otherwise */ bool dirty(); /** * Close the currently opened file and create an empty new file. * * @see MyMoneyFile */ void newFile(); /** * This method enables the state of all views (except home view) according * to an open file. */ void enableViewsIfFileOpen(); void addWidget(QWidget* w); void showPage(KPageWidgetItem* pageItem); /** * check if the current view allows to print something * * @retval true Yes, view allows to print * @retval false No, view cannot print */ bool canPrint(); void finishReconciliation(const MyMoneyAccount& account); /** * This method updates names of currencies from file to localized names */ void updateCurrencyNames(); /** * This method loads all known currencies and saves them to the storage */ void loadAllCurrencies(); void showTitleBar(bool show); /** * This method changes the view type according to the settings. */ void updateViewType(); void slotAccountTreeViewChanged(const eAccountsModel::Column column, const bool show); void setOnlinePlugins(QMap& plugins); // TODO: remove that function /** * ugly proxy function */ eDialogs::ScheduleResultCode enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys); protected: /** * Overwritten because KMyMoney has it's custom header. */ virtual bool showPageHeader() const; public Q_SLOTS: /** * This slot writes information about the page passed as argument @a current * in the kmymoney.rc file so that it can be selected automatically when * the application is started again. * * @param current QModelIndex of the current page item * @param previous QModelIndex of the previous page item */ void slotCurrentPageChanged(const QModelIndex current, const QModelIndex previous); /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ void slotRefreshViews(); /** * Called, whenever the payees view should pop up and a specific * transaction in an account should be shown. * * @param payeeId The ID of the payee to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotPayeeSelected(const QString& payeeId, const QString& accountId, const QString& transactionId); /** * Called, whenever the tags view should pop up and a specific * transaction in an account should be shown. * * @param tagId The ID of the tag to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotTagSelected(const QString& tagId, const QString& accountId, const QString& transactionId); /** * This slot prints the current view. */ void slotPrintView(); /** * Called when the user changes the detail * setting of the transaction register * * @param detailed if true, the register is shown with all details */ void slotShowTransactionDetail(bool detailed); /** * Informs respective views about selected object, so they can * update action states and current object. * @param obj Account, Category, Investment, Stock, Institution */ void slotObjectSelected(const MyMoneyObject& obj); void slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& list); private Q_SLOTS: /** * This slots switches the view to the specific page */ void slotShowHomePage(); void slotShowInstitutionsPage(); void slotShowAccountsPage(); void slotShowSchedulesPage(); void slotShowCategoriesPage(); void slotShowTagsPage(); void slotShowPayeesPage(); void slotShowLedgersPage(); void slotShowInvestmentsPage(); void slotShowReportsPage(); void slotShowBudgetPage(); void slotShowForecastPage(); void slotShowOutboxPage(); /** * Opens object in ledgers or edits in case of institution * @param obj Account, Category, Investment, Stock, Institution */ void slotOpenObjectRequested(const MyMoneyObject& obj); /** * Opens context menu based on objects's type * @param obj Account, Category, Investment, Stock, Institution */ void slotContextMenuRequested(const MyMoneyObject& obj); void slotTransactionsMenuRequested(const KMyMoneyRegister::SelectedTransactions& list); void slotSwitchView(View view); protected Q_SLOTS: /** * eventually replace this with KMyMoneyApp::slotCurrencySetBase(). * it contains the same code * * @deprecated */ void slotSetBaseCurrency(const MyMoneySecurity& baseCurrency); private: /** * This method is called from readFile to open a database file which * is to be processed in 'proper' database mode, i.e. in-place updates * * @param dbaseURL pseudo-QUrl representation of database * * @retval true Database opened successfully * @retval false Could not open or read database */ bool openDatabase(const QUrl &dbaseURL); /** * This method is used after a file or database has been * read into storage, and performs various initialization tasks * * @retval true all went okay * @retval false an exception occurred during this process */ bool initializeStorage(); /** * This method is used by saveFile() to store the data * either directly in the destination file if it is on * the local file system or in a temporary file when * the final destination is reached over a network * protocol (e.g. FTP) * * @param localFile the name of the local file * @param writer pointer to the formatter * @param plaintext whether to override any compression & encryption settings * @param keyList QString containing a comma separated list of keys to be used for encryption * If @p keyList is empty, the file will be saved unencrypted * * @note This method will close the file when it is written. */ - void saveToLocalFile(const QString& localFile, IMyMoneyStorageFormat* writer, bool plaintext = false, const QString& keyList = QString()); + void saveToLocalFile(const QString& localFile, IMyMoneyOperationsFormat* writer, bool plaintext = false, const QString& keyList = QString()); /** * Internal method used by slotAccountNew() and slotAccountCategory(). */ void accountNew(const bool createCategory); void resetViewSelection(const View); void connectView(const View); Q_SIGNALS: /** * This signal is emitted whenever a view is selected. * The parameter @p view is identified as one of KMyMoneyView::viewID. */ void viewActivated(int view); /** * This signal is emitted whenever a new view is about to be selected. */ void aboutToChangeView(); void accountSelectedForContextMenu(const MyMoneyAccount& acc); void viewStateChanged(bool enabled); /** * This signal is emitted to inform the kmmFile plugin when various file actions * occur. The Action parameter distinguishes between them. */ void kmmFilePlugin(unsigned int action); /** * This signal is emitted after a data source has been closed */ void fileClosed(); /** * This signal is emitted after a data source has been opened */ void fileOpened(); /** * @brief proxy signal */ void statusMsg(const QString& txt); /** * @brief proxy signal */ void statusProgress(int cnt, int base); void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& account); }; #endif